From 64de0b3628088ba3817fd68f84b3aaaef6c86e83 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:19:17 +0200 Subject: [PATCH 1/5] :recycle: cleanup and uniformize RAG search implementation with other SDKs --- docs/v2/client_options.rst | 12 ++- docs/v2/index.rst | 1 + docs/v2/mindee_http.rst | 6 -- docs/v2/parsing/search.rst | 13 +++- docs/v2/search.rst | 26 +++++++ mindee/cli.py | 6 +- .../client_options/base_search_parameters.py | 4 +- mindee/v2/commands/__init__.py | 2 + mindee/v2/commands/cli_parser.py | 42 ++++++---- mindee/v2/commands/search_models_command.py | 9 ++- .../commands/search_rag_documents_command.py | 76 +++++++++++++++++++ .../inference/inference_active_options.py | 19 ++--- mindee/v2/parsing/inference/rag_metadata.py | 1 + .../v2/parsing/search/base_search_response.py | 4 +- mindee/v2/parsing/search/model_webhook.py | 2 +- .../v2/parsing/search/pagination_metadata.py | 4 +- mindee/v2/parsing/search/search_model.py | 20 ++++- mindee/v2/parsing/search/search_models.py | 3 +- mindee/v2/parsing/search/search_response.py | 11 ++- .../params/extraction_parameters.py | 8 +- .../search/models/model_search_parameters.py | 4 +- .../v2/search/models/model_search_response.py | 4 +- .../rag_document_search_parameters.py | 6 +- .../rag_document_search_response.py | 6 +- tests/v2/test_cli.py | 45 ++++++++++- 25 files changed, 261 insertions(+), 73 deletions(-) create mode 100644 docs/v2/search.rst create mode 100644 mindee/v2/commands/search_rag_documents_command.py diff --git a/docs/v2/client_options.rst b/docs/v2/client_options.rst index 22842a12..d77249b2 100644 --- a/docs/v2/client_options.rst +++ b/docs/v2/client_options.rst @@ -1,8 +1,14 @@ V2 Client Options ################# -Base Parameters -=============== -.. autoclass:: mindee.v2.client_options.base_parameters.BaseParameters +Base Product Parameters +======================= +.. autoclass:: mindee.v2.client_options.base_product_parameters.BaseProductParameters + :members: + :inherited-members: + +Base Search Parameters +====================== +.. autoclass:: mindee.v2.client_options.base_search_parameters.BaseSearchParameters :members: :inherited-members: diff --git a/docs/v2/index.rst b/docs/v2/index.rst index 2f3aed59..6530183f 100644 --- a/docs/v2/index.rst +++ b/docs/v2/index.rst @@ -10,6 +10,7 @@ V2 Utilities ./mindee_http ./parsing/index ./product/index + ./search diff --git a/docs/v2/mindee_http.rst b/docs/v2/mindee_http.rst index 5037d5c8..3a77d915 100644 --- a/docs/v2/mindee_http.rst +++ b/docs/v2/mindee_http.rst @@ -6,9 +6,3 @@ Mindee API V2 .. autoclass:: mindee.v2.mindee_http.mindee_api_v2.MindeeAPIV2 :members: :inherited-members: - -Response Validation V2 -====================== -.. automodule:: mindee.v2.mindee_http.response_validation_v2 - :members: - :inherited-members: diff --git a/docs/v2/parsing/search.rst b/docs/v2/parsing/search.rst index f220952e..5f42fec2 100644 --- a/docs/v2/parsing/search.rst +++ b/docs/v2/parsing/search.rst @@ -28,9 +28,20 @@ Search Models :members: :inherited-members: +Search RAG Document +################### +.. autoclass:: mindee.v2.parsing.search.search_rag_document.SearchRagDocument + :members: + :inherited-members: + +Search RAG Documents +#################### +.. autoclass:: mindee.v2.parsing.search.search_rag_documents.SearchRagDocuments + :members: + :inherited-members: + Search Response ############### .. autoclass:: mindee.v2.parsing.search.search_response.SearchResponse :members: :inherited-members: - diff --git a/docs/v2/search.rst b/docs/v2/search.rst new file mode 100644 index 00000000..f5b3a24e --- /dev/null +++ b/docs/v2/search.rst @@ -0,0 +1,26 @@ +V2 Search +######### + +Model Search Parameters +======================= +.. autoclass:: mindee.v2.search.models.model_search_parameters.ModelSearchParameters + :members: + :inherited-members: + +Model Search Response +===================== +.. autoclass:: mindee.v2.search.models.model_search_response.ModelSearchResponse + :members: + :inherited-members: + +RAG Document Search Parameters +============================== +.. autoclass:: mindee.v2.search.rag_documents.rag_document_search_parameters.RagDocumentSearchParameters + :members: + :inherited-members: + +RAG Document Search Response +============================ +.. autoclass:: mindee.v2.search.rag_documents.rag_document_search_response.RagDocumentSearchResponse + :members: + :inherited-members: diff --git a/mindee/cli.py b/mindee/cli.py index 51a29983..73664a6e 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -63,9 +63,9 @@ def main() -> None: """Run the Command Line Interface. The unified ``mindee`` binary exposes V2 inference commands and the - ``search-models`` utility at the root, with all V1 product commands - wrapped under a ``v1`` subcommand — mirroring the canonical - ``mindee-api-dotnet`` CLI. + ``search-models`` and ``search-rag-docs`` utilities at the root, with + all V1 product commands wrapped under a ``v1`` subcommand — mirroring + the canonical ``mindee-api-dotnet`` CLI. Pass ``--verbose`` (or ``-v``) to enable diagnostic logging; repeat the flag (``--verbose --verbose``) for debug-level output. diff --git a/mindee/v2/client_options/base_search_parameters.py b/mindee/v2/client_options/base_search_parameters.py index 2fe06c53..e6007626 100644 --- a/mindee/v2/client_options/base_search_parameters.py +++ b/mindee/v2/client_options/base_search_parameters.py @@ -31,9 +31,9 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: """ data: dict[str, str | list[str]] = {} - if self.page is not None: + if self.page is not None and self.page > 0: data["page"] = str(self.page) - if self.per_page is not None: + if self.per_page is not None and self.per_page > 0: data["per_page"] = str(self.per_page) return data diff --git a/mindee/v2/commands/__init__.py b/mindee/v2/commands/__init__.py index aa51ebe8..5959c3b3 100644 --- a/mindee/v2/commands/__init__.py +++ b/mindee/v2/commands/__init__.py @@ -6,6 +6,7 @@ from mindee.v2.commands.ocr_command import OcrCommand from mindee.v2.commands.output_type import OutputType from mindee.v2.commands.search_models_command import SearchModelsCommand +from mindee.v2.commands.search_rag_documents_command import SearchRagDocumentsCommand from mindee.v2.commands.split_command import SplitCommand __all__ = [ @@ -18,5 +19,6 @@ "OcrCommand", "OutputType", "SearchModelsCommand", + "SearchRagDocumentsCommand", "SplitCommand", ] diff --git a/mindee/v2/commands/cli_parser.py b/mindee/v2/commands/cli_parser.py index bae32429..028fb48b 100644 --- a/mindee/v2/commands/cli_parser.py +++ b/mindee/v2/commands/cli_parser.py @@ -16,6 +16,7 @@ from mindee.v2.commands.extraction_command import ExtractionCommand from mindee.v2.commands.ocr_command import OcrCommand from mindee.v2.commands.search_models_command import SearchModelsCommand +from mindee.v2.commands.search_rag_documents_command import SearchRagDocumentsCommand from mindee.v2.commands.split_command import SplitCommand from mindee.v2.error.mindee_api_v2_error import MindeeAPIV2Error @@ -51,7 +52,7 @@ class MindeeParser: * V2 inference commands are exposed at the root level (``classification``, ``crop``, ``extraction``, ``ocr``, ``split``). - * The ``search-models`` utility is also at the root. + * The ``search-models`` and ``search-rag-docs`` utilities are also at the root. * V1 product commands are wrapped under a ``v1`` subcommand. """ @@ -60,6 +61,7 @@ class MindeeParser: _client_factory: Callable[[str | None], Client] _inference_commands: dict[str, BaseInferenceCommand] _search_models_command: SearchModelsCommand + _search_rag_documents_command: SearchRagDocumentsCommand def __init__( self, @@ -76,6 +78,7 @@ def __init__( cmd.name: cmd for cmd in _build_inference_commands() } self._search_models_command = SearchModelsCommand() + self._search_rag_documents_command = SearchRagDocumentsCommand() if parsed_args is None: self._build_parser() self.parsed_args = self.parser.parse_args() @@ -95,27 +98,33 @@ def call_parse(self) -> int: self.parser.print_help() return 1 try: - if cmd == "v1": - v1_parser = V1MindeeParser(parsed_args=self.parsed_args) - v1_parser.call_parse() - return 0 - if cmd == self._search_models_command.name: - return self._search_models_command.execute( - self.parsed_args, self._client_factory - ) - inference_command = self._inference_commands.get(cmd) - if inference_command is None: - raise ValueError(f"Unknown command: {cmd}") - return inference_command.execute(self.parsed_args, self._client_factory) + return self._execute_command(cmd) except MindeeAPIV2Error as exc: return _report_api_key_error(exc, "V2", "MINDEE_V2_API_KEY") except MindeeAPIError as exc: return _report_api_key_error(exc, "V1", "MINDEE_API_KEY") + def _execute_command(self, cmd: str) -> int: + """Execute a parsed subcommand.""" + if cmd == "v1": + v1_parser = V1MindeeParser(parsed_args=self.parsed_args) + v1_parser.call_parse() + return 0 + if cmd == self._search_models_command.name: + return self._search_models_command.execute( + self.parsed_args, self._client_factory + ) + if cmd == self._search_rag_documents_command.name: + return self._search_rag_documents_command.execute( + self.parsed_args, self._client_factory + ) + + inference_command = self._inference_commands.get(cmd) + if inference_command is None: + raise ValueError(f"Unknown command: {cmd}") + return inference_command.execute(self.parsed_args, self._client_factory) + def _build_parser(self) -> None: - # ``--verbose`` / ``-v`` are pre-consumed in ``mindee.cli.main`` - # (mirroring the .NET ``args.Contains("--verbose")`` pattern); we - # still register them here for ``--help`` discoverability. self.parser.add_argument( "-v", "--verbose", @@ -129,6 +138,7 @@ def _build_parser(self) -> None: cmd.register(subparsers) self._search_models_command.register(subparsers) + self._search_rag_documents_command.register(subparsers) v1_parser = subparsers.add_parser( "v1", diff --git a/mindee/v2/commands/search_models_command.py b/mindee/v2/commands/search_models_command.py index c8bd465e..acd4465e 100644 --- a/mindee/v2/commands/search_models_command.py +++ b/mindee/v2/commands/search_models_command.py @@ -2,6 +2,7 @@ from collections.abc import Callable from mindee.v2.client import Client +from mindee.v2.search.models.model_search_parameters import ModelSearchParameters _AVAILABLE_MODEL_TYPES: list[str] = [ "extraction", @@ -74,9 +75,11 @@ def execute( ) -> int: """Run the search and print the result.""" client = client_factory(getattr(parsed_args, "api_key", None)) - response = client.search_models( - name=getattr(parsed_args, "name", None), - model_type=getattr(parsed_args, "model_type", None), + response = client.search( + ModelSearchParameters( + name=getattr(parsed_args, "name", None), + model_type=getattr(parsed_args, "model_type", None), + ) ) if getattr(parsed_args, "raw_json", False): print(response.raw_http) diff --git a/mindee/v2/commands/search_rag_documents_command.py b/mindee/v2/commands/search_rag_documents_command.py new file mode 100644 index 00000000..82744138 --- /dev/null +++ b/mindee/v2/commands/search_rag_documents_command.py @@ -0,0 +1,76 @@ +from argparse import ArgumentParser, Namespace, _SubParsersAction +from collections.abc import Callable + +from mindee.v2.client import Client +from mindee.v2.search.rag_documents.rag_document_search_parameters import ( + RagDocumentSearchParameters, +) + + +class SearchRagDocumentsCommand: + """Builder + handler for the V2 ``search-rag-docs`` subcommand. + + Mirrors ``Mindee.Cli.Commands.V2.SearchRagDocumentsCommand`` from the + .NET SDK. + """ + + name = "search-rag-docs" + description = "Search available RAG documents for a given model." + + def register(self, subparsers: _SubParsersAction) -> ArgumentParser: + """Register this command on the given subparsers action.""" + parser = subparsers.add_parser( + self.name, + help=self.description, + description=self.description, + ) + parser.add_argument( + "-k", + "--api-key", + dest="api_key", + help="Mindee V2 API key.", + required=False, + default=None, + ) + parser.add_argument( + "-m", + "--model-id", + dest="model_id", + help="Filter by model ID", + required=True, + ) + parser.add_argument( + "-f", + "--filename", + dest="filename", + help="Filter by file name partial match (case insensitive).", + required=False, + default=None, + ) + parser.add_argument( + "-r", + "--raw-json", + dest="raw_json", + action="store_true", + help="Whether to output the raw JSON response.", + ) + return parser + + def execute( + self, + parsed_args: Namespace, + client_factory: Callable[[str | None], Client], + ) -> int: + """Run the search and print the result.""" + client = client_factory(getattr(parsed_args, "api_key", None)) + response = client.search( + RagDocumentSearchParameters( + model_id=parsed_args.model_id, + filename=getattr(parsed_args, "filename", None), + ) + ) + if getattr(parsed_args, "raw_json", False): + print(response.raw_http) + else: + print(response) + return 0 diff --git a/mindee/v2/parsing/inference/inference_active_options.py b/mindee/v2/parsing/inference/inference_active_options.py index 4c894aef..1fd46e9e 100644 --- a/mindee/v2/parsing/inference/inference_active_options.py +++ b/mindee/v2/parsing/inference/inference_active_options.py @@ -17,25 +17,16 @@ class InferenceActiveOptions: """Active options for the inference.""" raw_text: bool - """ - Whether the Raw Text feature was activated. - When this feature is activated, the raw text extracted from the document is returned in the result. - """ + """Extract the full text content from the document as strings, and fill the ``raw_text`` attribute.""" polygon: bool - """ - Whether the polygon feature was activated. - When this feature is activated, the bounding-box polygon(s) for each field is returned in the result. - """ + """Calculate bounding box polygons for all fields, and fill their ``locations`` attribute.""" confidence: bool """ - Whether the confidence feature was activated. - When this feature is activated, a confidence score for each field is returned in the result. + Boost the precision and accuracy of all extractions. + Calculate confidence scores for all fields, and fill their ``confidence`` attribute. """ rag: bool - """ - Whether the Retrieval-Augmented Generation feature was activated. - When this feature is activated, the RAG pipeline is used to increase result accuracy. - """ + """Enhance extraction accuracy with Retrieval-Augmented Generation.""" text_context: bool """ Whether the text context feature was activated. diff --git a/mindee/v2/parsing/inference/rag_metadata.py b/mindee/v2/parsing/inference/rag_metadata.py index c9af29b5..52e0a4f4 100644 --- a/mindee/v2/parsing/inference/rag_metadata.py +++ b/mindee/v2/parsing/inference/rag_metadata.py @@ -5,6 +5,7 @@ class RAGMetadata: """Metadata about the RAG operation.""" retrieved_document_id: str | None + """The UUID of the matched document used during the RAG operation.""" def __init__(self, raw_response: StringDict): self.retrieved_document_id = raw_response["retrieved_document_id"] diff --git a/mindee/v2/parsing/search/base_search_response.py b/mindee/v2/parsing/search/base_search_response.py index 3d0eb1f5..3be4057a 100644 --- a/mindee/v2/parsing/search/base_search_response.py +++ b/mindee/v2/parsing/search/base_search_response.py @@ -9,7 +9,7 @@ class BaseSearchResponse(CommonResponse, ABC): """Base class for search responses.""" pagination: PaginationMetadata - """Pagination metadata for the search results.""" + """Pagination metadata.""" def __init__(self, raw_response: StringDict) -> None: super().__init__(raw_response) @@ -17,7 +17,7 @@ def __init__(self, raw_response: StringDict) -> None: @abstractmethod def body_lines(self) -> list[str]: - """List of strings representing the search response.""" + """Lines composing the response-specific body (header + items).""" def __str__(self) -> str: """ diff --git a/mindee/v2/parsing/search/model_webhook.py b/mindee/v2/parsing/search/model_webhook.py index d4d3bd88..01065a07 100644 --- a/mindee/v2/parsing/search/model_webhook.py +++ b/mindee/v2/parsing/search/model_webhook.py @@ -1,5 +1,5 @@ class ModelWebhook: - """Model webhook information.""" + """Information about a model's webhook.""" id: str """ID of the webhook.""" diff --git a/mindee/v2/parsing/search/pagination_metadata.py b/mindee/v2/parsing/search/pagination_metadata.py index 14e628e3..29e9a012 100644 --- a/mindee/v2/parsing/search/pagination_metadata.py +++ b/mindee/v2/parsing/search/pagination_metadata.py @@ -1,12 +1,12 @@ class PaginationMetadata: - """Pagination metadata.""" + """Pagination metadata associated with model search.""" per_page: int """Number of results per page.""" page: int """1-indexed page number.""" total_items: int - """Total number of items.""" + """Total items.""" total_pages: int """Total number of pages.""" total_items_unfiltered: int | None diff --git a/mindee/v2/parsing/search/search_model.py b/mindee/v2/parsing/search/search_model.py index 97be009c..2f2c9e30 100644 --- a/mindee/v2/parsing/search/search_model.py +++ b/mindee/v2/parsing/search/search_model.py @@ -6,13 +6,13 @@ class SearchModel: """Individual model information.""" id: str - """Model ID.""" + """ID of the model.""" name: str - """Model name.""" + """Name of the model.""" model_type: str - """Model type.""" + """Type of the model.""" webhooks: list[ModelWebhook] - """Webhooks associated with the model.""" + """List of webhooks associated with the model.""" def __init__(self, server_response: StringDict) -> None: self.id = server_response["id"] @@ -23,3 +23,15 @@ def __init__(self, server_response: StringDict) -> None: if "webhooks" in server_response else [] ) + + def __str__(self) -> str: + """String representation of the model.""" + return f":Name: {self.name}\n:ID: {self.id}\n:Model Type: {self.model_type}" + + def to_list_string(self) -> list[str]: + """String representation of the model.""" + return [ + f":Name: {self.name}", + f":ID: {self.id}", + f":Model Type: {self.model_type}", + ] diff --git a/mindee/v2/parsing/search/search_models.py b/mindee/v2/parsing/search/search_models.py index c7c907d6..86c135dc 100644 --- a/mindee/v2/parsing/search/search_models.py +++ b/mindee/v2/parsing/search/search_models.py @@ -2,7 +2,7 @@ class SearchModels(list[SearchModel]): - """List of models.""" + """List of search models.""" def __init__(self, raw_response: list[dict]) -> None: super().__init__([SearchModel(item) for item in raw_response]) @@ -19,6 +19,5 @@ def __str__(self) -> str: lines.append(f"* :Name: {model.name}") lines.append(f" :ID: {model.id}") lines.append(f" :Model Type: {model.model_type}") - lines.append(f" :Webhooks: {len(model.webhooks)}") return "\n".join(lines) + "\n" diff --git a/mindee/v2/parsing/search/search_response.py b/mindee/v2/parsing/search/search_response.py index cba1baec..d7d17815 100644 --- a/mindee/v2/parsing/search/search_response.py +++ b/mindee/v2/parsing/search/search_response.py @@ -2,4 +2,13 @@ class SearchResponse(ModelSearchResponse): - """Deprecated: use `ModelSearchResponse` instead.""" + """Models search response.""" + + @property + def pagination_metadata(self): + """Pagination metadata (Obsolete).""" + return self.pagination + + @pagination_metadata.setter + def pagination_metadata(self, value): + self.pagination = value diff --git a/mindee/v2/product/extraction/params/extraction_parameters.py b/mindee/v2/product/extraction/params/extraction_parameters.py index 7940a890..08e378ec 100644 --- a/mindee/v2/product/extraction/params/extraction_parameters.py +++ b/mindee/v2/product/extraction/params/extraction_parameters.py @@ -51,13 +51,13 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: if self.data_schema is not None: data["data_schema"] = str(self.data_schema) if self.rag is not None: - data["rag"] = data["rag"] = str(self.rag).lower() + data["rag"] = str(self.rag).lower() if self.raw_text is not None: - data["raw_text"] = data["raw_text"] = str(self.raw_text).lower() + data["raw_text"] = str(self.raw_text).lower() if self.polygon is not None: - data["polygon"] = data["polygon"] = str(self.polygon).lower() + data["polygon"] = str(self.polygon).lower() if self.confidence is not None: - data["confidence"] = data["confidence"] = str(self.confidence).lower() + data["confidence"] = str(self.confidence).lower() if self.text_context is not None: data["text_context"] = self.text_context return data diff --git a/mindee/v2/search/models/model_search_parameters.py b/mindee/v2/search/models/model_search_parameters.py index 063a6f68..ee89c9e1 100644 --- a/mindee/v2/search/models/model_search_parameters.py +++ b/mindee/v2/search/models/model_search_parameters.py @@ -23,9 +23,9 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: params = super().get_request_parameters() - if self.name is not None: + if self.name: params["name"] = self.name - if self.model_type is not None: + if self.model_type: params["model_type"] = self.model_type return params diff --git a/mindee/v2/search/models/model_search_response.py b/mindee/v2/search/models/model_search_response.py index 549adbf2..a273489c 100644 --- a/mindee/v2/search/models/model_search_response.py +++ b/mindee/v2/search/models/model_search_response.py @@ -7,12 +7,12 @@ class ModelSearchResponse(BaseSearchResponse): """Models search response.""" models: SearchModels - """Paginated list of matching models.""" + """List of all models matching the search query.""" def __init__(self, raw_response: StringDict) -> None: super().__init__(raw_response) self.models = SearchModels(raw_response["models"]) def body_lines(self) -> list[str]: - """List of strings representing the search response.""" + """Lines composing the response-specific body (header + items).""" return ["Models", "######", str(self.models)] diff --git a/mindee/v2/search/rag_documents/rag_document_search_parameters.py b/mindee/v2/search/rag_documents/rag_document_search_parameters.py index 02ace8a7..e261da31 100644 --- a/mindee/v2/search/rag_documents/rag_document_search_parameters.py +++ b/mindee/v2/search/rag_documents/rag_document_search_parameters.py @@ -20,12 +20,16 @@ class RagDocumentSearchParameters(BaseSearchParameters[RagDocumentSearchResponse _slug: ClassVar[str] = "rag-documents" _response_class: type[RagDocumentSearchResponse] = RagDocumentSearchResponse + def __post_init__(self) -> None: + if not self.model_id: + raise ValueError("ModelId is required in RagDocumentSearchParameters") + def get_request_parameters(self) -> dict[str, str | list[str]]: params = super().get_request_parameters() params["model_id"] = self.model_id - if self.filename is not None: + if self.filename: params["filename"] = self.filename return params diff --git a/mindee/v2/search/rag_documents/rag_document_search_response.py b/mindee/v2/search/rag_documents/rag_document_search_response.py index 77e48c09..911ea971 100644 --- a/mindee/v2/search/rag_documents/rag_document_search_response.py +++ b/mindee/v2/search/rag_documents/rag_document_search_response.py @@ -4,7 +4,7 @@ class RagDocumentSearchResponse(BaseSearchResponse): - """RAG documents search response.""" + """RAG Documents search response.""" rag_documents: SearchRagDocuments """Paginated list of matching RAG documents.""" @@ -14,5 +14,5 @@ def __init__(self, raw_response: StringDict) -> None: self.rag_documents = SearchRagDocuments(raw_response["rag_documents"]) def body_lines(self) -> list[str]: - """List of strings representing the search response.""" - return ["RAG Documents", "################", str(self.rag_documents)] + """Lines composing the response-specific body (header + items).""" + return ["RAG Documents", "#############", str(self.rag_documents)] diff --git a/tests/v2/test_cli.py b/tests/v2/test_cli.py index 2dc74f31..13ee6693 100644 --- a/tests/v2/test_cli.py +++ b/tests/v2/test_cli.py @@ -26,16 +26,20 @@ def parser() -> MindeeParser: from mindee.v2.commands.search_models_command import ( SearchModelsCommand, ) + from mindee.v2.commands.search_rag_documents_command import ( + SearchRagDocumentsCommand, + ) p._inference_commands = {cmd.name: cmd for cmd in _build_inference_commands()} p._search_models_command = SearchModelsCommand() + p._search_rag_documents_command = SearchRagDocumentsCommand() p._client_factory = _default_client_factory p._build_parser() return p def test_top_level_subcommands_registered(parser: MindeeParser): - """All V2 inference subcommands + search-models + v1 are reachable.""" + """All V2 inference subcommands + search commands + v1 are reachable.""" expected = { "classification", "crop", @@ -43,6 +47,7 @@ def test_top_level_subcommands_registered(parser: MindeeParser): "ocr", "split", "search-models", + "search-rag-docs", "v1", } actions = [a for a in parser.parser._actions if a.dest == "cmd"] @@ -154,6 +159,26 @@ def test_search_models_rejects_invalid_model_type(parser: MindeeParser): parser.parser.parse_args(["search-models", "--model-type", "nope"]) +def test_search_rag_documents_flags(parser: MindeeParser): + ns = parser.parser.parse_args( + [ + "search-rag-docs", + "--api-key", + "dummy", + "--model-id", + "model-1", + "--filename", + "invoice", + "--raw-json", + ] + ) + assert ns.cmd == "search-rag-docs" + assert ns.api_key == "dummy" + assert ns.model_id == "model-1" + assert ns.filename == "invoice" + assert ns.raw_json is True + + def test_v1_group_dispatches_to_v1_product(parser: MindeeParser): """The `v1` group preserves the existing V1 product subcommand shape.""" ns = parser.parser.parse_args( @@ -213,6 +238,24 @@ def fake_execute(args, factory): assert captured == {"name": "inv", "model_type": "extraction"} +def test_search_rag_documents_dispatches_to_search_command( + monkeypatch, parser: MindeeParser +): + captured = {} + + def fake_execute(args, factory): + captured["model_id"] = args.model_id + captured["filename"] = args.filename + return 0 + + monkeypatch.setattr(parser._search_rag_documents_command, "execute", fake_execute) + parser.parsed_args = parser.parser.parse_args( + ["search-rag-docs", "-m", "model-1", "-f", "invoice"] + ) + parser.call_parse() + assert captured == {"model_id": "model-1", "filename": "invoice"} + + def test_v1_group_delegates_to_v1_mindee_parser(monkeypatch, parser: MindeeParser): """`v1` command instantiates the V1 MindeeParser with the parsed args.""" seen = {} From 56ab047060235ba2e3859f1f045c32eb521e7a2d Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:43:52 +0200 Subject: [PATCH 2/5] apply fixes --- mindee/v2/parsing/search/pagination_metadata.py | 5 +---- mindee/v2/parsing/search/search_model.py | 2 +- mindee/v2/parsing/search/search_response.py | 5 +++-- mindee/v2/search/models/model_search_response.py | 2 +- .../search/rag_documents/rag_document_search_parameters.py | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mindee/v2/parsing/search/pagination_metadata.py b/mindee/v2/parsing/search/pagination_metadata.py index 29e9a012..81292b7c 100644 --- a/mindee/v2/parsing/search/pagination_metadata.py +++ b/mindee/v2/parsing/search/pagination_metadata.py @@ -1,5 +1,5 @@ class PaginationMetadata: - """Pagination metadata associated with model search.""" + """Pagination metadata associated with searches.""" per_page: int """Number of results per page.""" @@ -9,15 +9,12 @@ class PaginationMetadata: """Total items.""" total_pages: int """Total number of pages.""" - total_items_unfiltered: int | None - """Total number of items, including unfiltered results.""" def __init__(self, server_response: dict) -> None: self.per_page = server_response["per_page"] self.page = server_response["page"] self.total_items = server_response["total_items"] self.total_pages = server_response["total_pages"] - self.total_items_unfiltered = server_response.get("total_items_unfiltered") def __str__(self) -> str: return ( diff --git a/mindee/v2/parsing/search/search_model.py b/mindee/v2/parsing/search/search_model.py index 2f2c9e30..ed036a8d 100644 --- a/mindee/v2/parsing/search/search_model.py +++ b/mindee/v2/parsing/search/search_model.py @@ -29,7 +29,7 @@ def __str__(self) -> str: return f":Name: {self.name}\n:ID: {self.id}\n:Model Type: {self.model_type}" def to_list_string(self) -> list[str]: - """String representation of the model.""" + """Return a list of display lines for multi-line rendering.""" return [ f":Name: {self.name}", f":ID: {self.id}", diff --git a/mindee/v2/parsing/search/search_response.py b/mindee/v2/parsing/search/search_response.py index d7d17815..2f232ab4 100644 --- a/mindee/v2/parsing/search/search_response.py +++ b/mindee/v2/parsing/search/search_response.py @@ -1,3 +1,4 @@ +from mindee.v2.parsing.search.pagination_metadata import PaginationMetadata from mindee.v2.search.models.model_search_response import ModelSearchResponse @@ -5,10 +6,10 @@ class SearchResponse(ModelSearchResponse): """Models search response.""" @property - def pagination_metadata(self): + def pagination_metadata(self) -> PaginationMetadata: """Pagination metadata (Obsolete).""" return self.pagination @pagination_metadata.setter - def pagination_metadata(self, value): + def pagination_metadata(self, value: PaginationMetadata) -> None: self.pagination = value diff --git a/mindee/v2/search/models/model_search_response.py b/mindee/v2/search/models/model_search_response.py index a273489c..bc73000b 100644 --- a/mindee/v2/search/models/model_search_response.py +++ b/mindee/v2/search/models/model_search_response.py @@ -7,7 +7,7 @@ class ModelSearchResponse(BaseSearchResponse): """Models search response.""" models: SearchModels - """List of all models matching the search query.""" + """Paginated list of matching models.""" def __init__(self, raw_response: StringDict) -> None: super().__init__(raw_response) diff --git a/mindee/v2/search/rag_documents/rag_document_search_parameters.py b/mindee/v2/search/rag_documents/rag_document_search_parameters.py index e261da31..351e3ff4 100644 --- a/mindee/v2/search/rag_documents/rag_document_search_parameters.py +++ b/mindee/v2/search/rag_documents/rag_document_search_parameters.py @@ -22,7 +22,7 @@ class RagDocumentSearchParameters(BaseSearchParameters[RagDocumentSearchResponse def __post_init__(self) -> None: if not self.model_id: - raise ValueError("ModelId is required in RagDocumentSearchParameters") + raise ValueError("model_id is required in RagDocumentSearchParameters") def get_request_parameters(self) -> dict[str, str | list[str]]: params = super().get_request_parameters() From be00f7f138140dd202802de103ad63a1d8336cc7 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:42:19 +0200 Subject: [PATCH 3/5] add check for search params --- mindee/v2/client_options/base_search_parameters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mindee/v2/client_options/base_search_parameters.py b/mindee/v2/client_options/base_search_parameters.py index e6007626..47995589 100644 --- a/mindee/v2/client_options/base_search_parameters.py +++ b/mindee/v2/client_options/base_search_parameters.py @@ -33,8 +33,12 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: if self.page is not None and self.page > 0: data["page"] = str(self.page) + else: + raise ValueError("page must be a positive integer") if self.per_page is not None and self.per_page > 0: data["per_page"] = str(self.per_page) + else: + raise ValueError("per_page must be a positive integer") return data From 5e6cec1d970cf63322b965017d521348b3e33dc5 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:43:34 +0200 Subject: [PATCH 4/5] fix checks --- .../client_options/base_search_parameters.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mindee/v2/client_options/base_search_parameters.py b/mindee/v2/client_options/base_search_parameters.py index 47995589..540bb4d7 100644 --- a/mindee/v2/client_options/base_search_parameters.py +++ b/mindee/v2/client_options/base_search_parameters.py @@ -31,14 +31,16 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: """ data: dict[str, str | list[str]] = {} - if self.page is not None and self.page > 0: - data["page"] = str(self.page) - else: - raise ValueError("page must be a positive integer") - if self.per_page is not None and self.per_page > 0: - data["per_page"] = str(self.per_page) - else: - raise ValueError("per_page must be a positive integer") + if self.page is not None: + if self.page > 0: + data["page"] = str(self.page) + else: + raise ValueError("page must be a positive integer") + if self.per_page is not None: + if self.per_page > 0: + data["per_page"] = str(self.per_page) + else: + raise ValueError("per_page must be a positive integer") return data From daec59bf3c7c62128cf810d800902ea87a12a597 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:29:24 +0200 Subject: [PATCH 5/5] fix many issues to match .NET implementation --- .pylintrc | 5 +- mindee/cli.py | 35 ++------ mindee/pdf/pdf_utils.py | 12 +-- mindee/v1/commands/cli_parser.py | 5 +- mindee/v2/client.py | 6 ++ .../client_options/base_search_parameters.py | 10 +-- mindee/v2/commands/base_inference_command.py | 30 ++----- mindee/v2/commands/cli_parser.py | 33 ++------ mindee/v2/commands/extraction_command.py | 7 +- mindee/v2/commands/search_models_command.py | 8 +- .../commands/search_rag_documents_command.py | 6 +- .../v2/parsing/search/base_search_response.py | 2 +- mindee/v2/parsing/search/model_webhook.py | 1 + .../v2/parsing/search/pagination_metadata.py | 3 +- mindee/v2/parsing/search/search_model.py | 2 +- mindee/v2/parsing/search/search_response.py | 23 +++++- .../extraction/params/string_data_class.py | 4 +- .../search/models/model_search_parameters.py | 6 +- scripts/generate_lite_toml.py | 8 +- tests/v1/extras/test_full_text_ocr.py | 17 +--- tests/v2/test_cli.py | 80 +++++++++---------- 21 files changed, 113 insertions(+), 190 deletions(-) diff --git a/.pylintrc b/.pylintrc index 4604c52f..6eb74154 100644 --- a/.pylintrc +++ b/.pylintrc @@ -120,7 +120,7 @@ argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. If left empty, argument names will be checked with the set # naming style. -#argument-rgx= +argument-rgx=[a-z_][a-z0-9_]{2,}$ # Naming style matching correct attribute names. attr-naming-style=snake_case @@ -189,6 +189,7 @@ function-naming-style=snake_case good-names=i, j, k, + e, ex, Run, _ @@ -249,7 +250,7 @@ variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- # naming-style. If left empty, variable names will be checked with the set # naming style. -#variable-rgx= +variable-rgx=[a-z_][a-z0-9_]{2,}$ [CLASSES] diff --git a/mindee/cli.py b/mindee/cli.py index 73664a6e..c956210c 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -9,12 +9,7 @@ def _find_v1_dashv_boundary(argv: list[str]) -> int | None: - """Locate the position after which ``-v`` belongs to V1 ``custom`` / - ``generated``. - - These two V1 products register ``-v/--version``; tokens at or beyond - the returned index must be left alone by the verbose pre-scan. - """ + """Find the index after which -v means --version for V1 custom/generated.""" for i, token in enumerate(argv): if token == "v1" and i + 1 < len(argv) and argv[i + 1] in _V1_DASHV_PRODUCTS: return i + 2 @@ -22,19 +17,10 @@ def _find_v1_dashv_boundary(argv: list[str]) -> int | None: def _extract_verbose_level(argv: list[str]) -> tuple[int, list[str]]: - """Pre-scan ``argv`` for ``--verbose`` / ``-v`` flags. - - Mirrors ``mindee-api-dotnet``'s ``args.Contains("--verbose")`` check: - the flag is consumed before argparse runs so it can appear anywhere - on the command line. - - * ``--verbose`` is consumed at any position (no conflict). - * ``-v`` is consumed at any position *except* after a ``v1 custom`` - or ``v1 generated`` invocation, where it is the V1 product's own - ``--version`` option. + """ + Consume --verbose / -v flags before argparse runs. - :returns: ``(level, remaining_argv)`` where ``level`` counts the number - of recognized verbose-flag occurrences. + :return: The verbose level and the remaining arguments. """ level = 0 remaining: list[str] = [] @@ -51,7 +37,7 @@ def _extract_verbose_level(argv: list[str]) -> tuple[int, list[str]]: def _configure_logging(verbose_level: int) -> None: - """Set the ``mindee`` logger level based on the verbose count.""" + """Set the Mindee logger level based on the verbose count.""" if verbose_level <= 0: return target = logging.INFO if verbose_level == 1 else logging.DEBUG @@ -60,16 +46,7 @@ def _configure_logging(verbose_level: int) -> None: def main() -> None: - """Run the Command Line Interface. - - The unified ``mindee`` binary exposes V2 inference commands and the - ``search-models`` and ``search-rag-docs`` utilities at the root, with - all V1 product commands wrapped under a ``v1`` subcommand — mirroring - the canonical ``mindee-api-dotnet`` CLI. - - Pass ``--verbose`` (or ``-v``) to enable diagnostic logging; repeat - the flag (``--verbose --verbose``) for debug-level output. - """ + """Run the Command Line Interface.""" stdout = cast(io.TextIOWrapper, sys.stdout) stdout_encoding = str(stdout.encoding) diff --git a/mindee/pdf/pdf_utils.py b/mindee/pdf/pdf_utils.py index de7cd833..f3ab2042 100644 --- a/mindee/pdf/pdf_utils.py +++ b/mindee/pdf/pdf_utils.py @@ -117,8 +117,8 @@ def _process_char( adjusted_box = _adjust_char_box(char_box, rotation, internal_height, internal_width) char_data_list: list[PDFCharData] = [] - for c in char_info["char"] or " ": - if c in ( + for char in char_info["char"] or " ": + if char in ( "\n", "\r", ): # Removes duplicated carriage returns in the PDF due to weird extraction. @@ -129,7 +129,7 @@ def _process_char( char_data_list.append( PDFCharData( - char=c, + char=char, left=int(adjusted_box[0]), right=int(adjusted_box[1]), top=int(adjusted_box[2]), @@ -288,13 +288,13 @@ def _adjust_char_box( return left, right, top, bottom -def lerp(start: float, end: float, t: float) -> float: +def lerp(start: float, end: float, factor: float) -> float: """ Performs linear interpolation between two numbers. :param start: The starting value. :param end: The ending value. - :param t: The interpolation factor (0 to 1). + :param factor: The interpolation factor (0 to 1). :return: The interpolated value. """ - return start * (1 - t) + end * t + return start * (1 - factor) + end * factor diff --git a/mindee/v1/commands/cli_parser.py b/mindee/v1/commands/cli_parser.py index 284c4df4..18822fc5 100644 --- a/mindee/v1/commands/cli_parser.py +++ b/mindee/v1/commands/cli_parser.py @@ -105,10 +105,7 @@ def add_custom_options(self) -> None: def register_v1_product_subparsers(parser: ArgumentParser) -> None: """ - Register V1 product subparsers under the given ``parser``. - - Used both by the legacy ``mindee`` binary and by the ``v1`` group of - the unified ``mindeeV2`` CLI. + Register V1 product subparsers under the given parser. """ parse_product_subparsers = parser.add_subparsers( dest="product_name", diff --git a/mindee/v2/client.py b/mindee/v2/client.py index 3608eb91..9fc6bebd 100644 --- a/mindee/v2/client.py +++ b/mindee/v2/client.py @@ -1,3 +1,4 @@ +import warnings from time import sleep from typing import TypeVar @@ -189,6 +190,11 @@ def search_models( """ Deprecated. Use `search` instead. """ + warnings.warn( + "search_models is deprecated, use search instead.", + DeprecationWarning, + stacklevel=2, + ) return self.mindee_api.req_get_search_models(name, model_type) def close(self) -> None: diff --git a/mindee/v2/client_options/base_search_parameters.py b/mindee/v2/client_options/base_search_parameters.py index 540bb4d7..e0919888 100644 --- a/mindee/v2/client_options/base_search_parameters.py +++ b/mindee/v2/client_options/base_search_parameters.py @@ -32,15 +32,13 @@ def get_request_parameters(self) -> dict[str, str | list[str]]: data: dict[str, str | list[str]] = {} if self.page is not None: - if self.page > 0: - data["page"] = str(self.page) - else: + if self.page <= 0: raise ValueError("page must be a positive integer") + data["page"] = str(self.page) if self.per_page is not None: - if self.per_page > 0: - data["per_page"] = str(self.per_page) - else: + if self.per_page <= 0: raise ValueError("per_page must be a positive integer") + data["per_page"] = str(self.per_page) return data diff --git a/mindee/v2/commands/base_inference_command.py b/mindee/v2/commands/base_inference_command.py index 3b385ce7..eccc01f1 100644 --- a/mindee/v2/commands/base_inference_command.py +++ b/mindee/v2/commands/base_inference_command.py @@ -16,24 +16,13 @@ class BaseInferenceCommand: - """Abstract base class for V2 inference CLI commands. - - Owns the options shared by every V2 inference product - (``path``, ``--model-id``, ``--api-key``, ``--alias``, ``--output``), - the input-source resolution, the client invocation, and the output - formatting. Each concrete subclass owns its own product-specific - options, builds the right :class:`BaseParameters` instance, and may - customize the human-readable output. - - Mirrors the canonical PHP implementation in - ``mindee-api-php/bin/V2/BaseInferenceCommand.php``. - """ + """Abstract base class for V2 inference CLI commands.""" name: str """Name of the subcommand (also used as product key).""" description: str - """Human-readable description shown in ``--help``.""" + """Human-readable description shown in the help.""" def register(self, subparsers: _SubParsersAction) -> ArgumentParser: """Register this command on the given subparsers action.""" @@ -92,19 +81,14 @@ def register(self, subparsers: _SubParsersAction) -> ArgumentParser: return parser def configure_product_options(self, parser: ArgumentParser) -> None: - """Hook for subclasses to add product-specific options. - - No-op by default. Override (for example in - :class:`~mindee.v2.commands.extraction_command.ExtractionCommand`) - to add flags only relevant to a single product. - """ + """Hook for subclasses to add product-specific options.""" def execute( self, parsed_args: Namespace, client_factory: Callable[[str | None], Client], ) -> int: - """Run the inference for ``parsed_args`` using ``client_factory``.""" + """Run the inference and print the result.""" api_key = getattr(parsed_args, "api_key", None) model_id = parsed_args.model_id webhook_ids = getattr(parsed_args, "webhook_ids", None) @@ -152,11 +136,7 @@ def get_summary(self, response) -> str: return str(inference.result) def get_full_output(self, parsed_args: Namespace, response) -> str: - """Detailed representation of an inference response. - - Defaults to the full inference dump; override to add - product-specific sections (raw text, RAG, ...). - """ + """Detailed representation of an inference response.""" del parsed_args inference = getattr(response, "inference", None) if inference is None: diff --git a/mindee/v2/commands/cli_parser.py b/mindee/v2/commands/cli_parser.py index 028fb48b..0803b0a2 100644 --- a/mindee/v2/commands/cli_parser.py +++ b/mindee/v2/commands/cli_parser.py @@ -25,16 +25,11 @@ class MindeeArgumentParser(ArgumentParser): - """Top-level argument parser for the unified ``mindee`` CLI.""" + """Top-level argument parser for the unified Mindee CLI.""" def _build_inference_commands() -> list[BaseInferenceCommand]: - """Return a fresh list of V2 inference command instances. - - Add a new product by appending its command class here. Each command - owns its own options, parameters and output formatting; there is no - central registry to keep in sync. - """ + """Return a fresh list of V2 inference command instances.""" return [ ClassificationCommand(), CropCommand(), @@ -45,16 +40,7 @@ def _build_inference_commands() -> list[BaseInferenceCommand]: class MindeeParser: - """ - Top-level parser for the unified ``mindee`` CLI. - - The shape mirrors the .NET ``Mindee.Cli`` binary: - - * V2 inference commands are exposed at the root level - (``classification``, ``crop``, ``extraction``, ``ocr``, ``split``). - * The ``search-models`` and ``search-rag-docs`` utilities are also at the root. - * V1 product commands are wrapped under a ``v1`` subcommand. - """ + """Top-level parser for the unified Mindee CLI.""" parser: MindeeArgumentParser parsed_args: Namespace @@ -87,11 +73,7 @@ def __init__( self._client_factory = client_factory or _default_client_factory def call_parse(self) -> int: - """Dispatch the parsed command to its handler. - - :returns: The exit code (``0`` on success, ``1`` on a recoverable - CLI error such as a missing API key). - """ + """Dispatch the parsed command to its handler and return the exit code.""" cmd = getattr(self.parsed_args, "cmd", None) if cmd is None: print("Please specify a subcommand.\n") @@ -105,7 +87,6 @@ def call_parse(self) -> int: return _report_api_key_error(exc, "V1", "MINDEE_API_KEY") def _execute_command(self, cmd: str) -> int: - """Execute a parsed subcommand.""" if cmd == "v1": v1_parser = V1MindeeParser(parsed_args=self.parsed_args) v1_parser.call_parse() @@ -153,11 +134,7 @@ def _default_client_factory(api_key: str | None) -> Client: def _report_api_key_error(exc: Exception, version: str, env_var: str) -> int: - """Print a friendly missing-key message to stderr and return exit code 1. - - Mirrors the .NET CLI's handling of ``OptionsValidationException`` when - the API key cannot be resolved from the command line or the environment. - """ + """Print a friendly missing-key message to stderr and return exit code 1.""" message = str(exc) or "API key is missing." if "Missing API key" in message or "api key" in message.lower(): message = ( diff --git a/mindee/v2/commands/extraction_command.py b/mindee/v2/commands/extraction_command.py index b89d0299..cec8454e 100644 --- a/mindee/v2/commands/extraction_command.py +++ b/mindee/v2/commands/extraction_command.py @@ -6,12 +6,7 @@ class ExtractionCommand(BaseInferenceCommand): - """V2 CLI command for the generic all-purpose extraction utility. - - Owns the extraction-only flags (``--rag``, ``--raw-text``, - ``--confidence``, ``--polygon``, ``--text-context``) and prepends the - optional Raw Text / RAG sections to the ``full`` output. - """ + """V2 CLI command for the generic all-purpose extraction utility.""" name = "extraction" description = "Generic all-purpose extraction." diff --git a/mindee/v2/commands/search_models_command.py b/mindee/v2/commands/search_models_command.py index acd4465e..ab01f35d 100644 --- a/mindee/v2/commands/search_models_command.py +++ b/mindee/v2/commands/search_models_command.py @@ -14,11 +14,7 @@ class SearchModelsCommand: - """Builder + handler for the V2 ``search-models`` subcommand. - - Mirrors ``Mindee.Cli.Commands.V2.SearchModelsCommand`` from the .NET - SDK. - """ + """CLI command for searching available models.""" name = "search-models" description = "Search available models." @@ -47,7 +43,7 @@ def register(self, subparsers: _SubParsersAction) -> ArgumentParser: default=None, ) model_type_help = ( - "Filter by exact model type (case sensitive).\nAvailable options:\n - " + "Filter by exact model type.\nAvailable options:\n - " + "\n - ".join(_AVAILABLE_MODEL_TYPES) ) parser.add_argument( diff --git a/mindee/v2/commands/search_rag_documents_command.py b/mindee/v2/commands/search_rag_documents_command.py index 82744138..1b1cd0c4 100644 --- a/mindee/v2/commands/search_rag_documents_command.py +++ b/mindee/v2/commands/search_rag_documents_command.py @@ -8,11 +8,7 @@ class SearchRagDocumentsCommand: - """Builder + handler for the V2 ``search-rag-docs`` subcommand. - - Mirrors ``Mindee.Cli.Commands.V2.SearchRagDocumentsCommand`` from the - .NET SDK. - """ + """CLI command for searching available RAG documents for a given model.""" name = "search-rag-docs" description = "Search available RAG documents for a given model." diff --git a/mindee/v2/parsing/search/base_search_response.py b/mindee/v2/parsing/search/base_search_response.py index 3be4057a..f4446943 100644 --- a/mindee/v2/parsing/search/base_search_response.py +++ b/mindee/v2/parsing/search/base_search_response.py @@ -21,7 +21,7 @@ def body_lines(self) -> list[str]: def __str__(self) -> str: """ - String representation. + String representation of the search response. """ lines: list[str] = self.body_lines() lines += ["Pagination Metadata", "###################", str(self.pagination)] diff --git a/mindee/v2/parsing/search/model_webhook.py b/mindee/v2/parsing/search/model_webhook.py index 01065a07..b733223d 100644 --- a/mindee/v2/parsing/search/model_webhook.py +++ b/mindee/v2/parsing/search/model_webhook.py @@ -14,4 +14,5 @@ def __init__(self, server_response: dict) -> None: self.url = server_response["url"] def __str__(self) -> str: + """String representation of the webhook.""" return f":Name: {self.name}\n:ID: {self.id}\n:URL: {self.url}" diff --git a/mindee/v2/parsing/search/pagination_metadata.py b/mindee/v2/parsing/search/pagination_metadata.py index 81292b7c..69aba68e 100644 --- a/mindee/v2/parsing/search/pagination_metadata.py +++ b/mindee/v2/parsing/search/pagination_metadata.py @@ -2,7 +2,7 @@ class PaginationMetadata: """Pagination metadata associated with searches.""" per_page: int - """Number of results per page.""" + """Number of items per page.""" page: int """1-indexed page number.""" total_items: int @@ -17,6 +17,7 @@ def __init__(self, server_response: dict) -> None: self.total_pages = server_response["total_pages"] def __str__(self) -> str: + """String representation of the pagination metadata.""" return ( f":Per Page: {self.per_page}\n" f":Page: {self.page}\n" diff --git a/mindee/v2/parsing/search/search_model.py b/mindee/v2/parsing/search/search_model.py index ed036a8d..028315cc 100644 --- a/mindee/v2/parsing/search/search_model.py +++ b/mindee/v2/parsing/search/search_model.py @@ -29,7 +29,7 @@ def __str__(self) -> str: return f":Name: {self.name}\n:ID: {self.id}\n:Model Type: {self.model_type}" def to_list_string(self) -> list[str]: - """Return a list of display lines for multi-line rendering.""" + """String representation of the model as a list of lines.""" return [ f":Name: {self.name}", f":ID: {self.id}", diff --git a/mindee/v2/parsing/search/search_response.py b/mindee/v2/parsing/search/search_response.py index 2f232ab4..57156a3c 100644 --- a/mindee/v2/parsing/search/search_response.py +++ b/mindee/v2/parsing/search/search_response.py @@ -1,15 +1,36 @@ +import warnings + +from mindee.parsing.common.string_dict import StringDict from mindee.v2.parsing.search.pagination_metadata import PaginationMetadata from mindee.v2.search.models.model_search_response import ModelSearchResponse class SearchResponse(ModelSearchResponse): - """Models search response.""" + """Deprecated: use `ModelSearchResponse` instead.""" + + def __init__(self, raw_response: StringDict) -> None: + warnings.warn( + "SearchResponse is deprecated, use ModelSearchResponse instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(raw_response) @property def pagination_metadata(self) -> PaginationMetadata: """Pagination metadata (Obsolete).""" + warnings.warn( + "pagination_metadata is deprecated, use pagination instead.", + DeprecationWarning, + stacklevel=2, + ) return self.pagination @pagination_metadata.setter def pagination_metadata(self, value: PaginationMetadata) -> None: + warnings.warn( + "pagination_metadata is deprecated, use pagination instead.", + DeprecationWarning, + stacklevel=2, + ) self.pagination = value diff --git a/mindee/v2/product/extraction/params/string_data_class.py b/mindee/v2/product/extraction/params/string_data_class.py index 05f98d78..b47ab4c1 100644 --- a/mindee/v2/product/extraction/params/string_data_class.py +++ b/mindee/v2/product/extraction/params/string_data_class.py @@ -7,9 +7,9 @@ class StringDataClass: """Base class for dataclasses that can be serialized to JSON.""" @staticmethod - def _no_none_values(x) -> dict: + def _no_none_values(items) -> dict: """Don't include None values in the JSON output.""" - return {k: v for (k, v) in x if v is not None} + return {k: v for (k, v) in items if v is not None} def __str__(self) -> str: return json.dumps( diff --git a/mindee/v2/search/models/model_search_parameters.py b/mindee/v2/search/models/model_search_parameters.py index ee89c9e1..e5f02ad6 100644 --- a/mindee/v2/search/models/model_search_parameters.py +++ b/mindee/v2/search/models/model_search_parameters.py @@ -10,17 +10,15 @@ class ModelSearchParameters(BaseSearchParameters[ModelSearchResponse]): """Search parameters for models.""" name: str | None = None - """Case-insensitive search term for the model name""" + """Case-insensitive search term for the model name.""" model_type: str | None = None - """Case-insensitive search term for the model type""" + """Case-insensitive search term for the model type.""" _slug: ClassVar[str] = "models" _response_class: type[ModelSearchResponse] = ModelSearchResponse def get_request_parameters(self) -> dict[str, str | list[str]]: - """Return the parameters for the request.""" - params = super().get_request_parameters() if self.name: diff --git a/scripts/generate_lite_toml.py b/scripts/generate_lite_toml.py index c8778d6a..637150d1 100644 --- a/scripts/generate_lite_toml.py +++ b/scripts/generate_lite_toml.py @@ -5,8 +5,8 @@ def generate_lite() -> None: """Generates the mindee-lite version of pyproject.toml""" - with open("pyproject.toml", encoding="utf-8") as f: - data: dict[str, Any] = toml.load(f) + with open("pyproject.toml", encoding="utf-8") as file_data: + data: dict[str, Any] = toml.load(file_data) data["project"]["name"] = "mindee-lite" data["project"]["description"] = ( @@ -32,8 +32,8 @@ def generate_lite() -> None: "ini_options" ]["addopts"].replace(" lite", " pypdfium2 and not pillow") - with open("pyproject-lite.toml", "w", encoding="utf-8") as f: - toml.dump(data, f) + with open("pyproject-lite.toml", "w", encoding="utf-8") as file_data: + toml.dump(data, file_data) print("Successfully generated pyproject-lite.toml") diff --git a/tests/v1/extras/test_full_text_ocr.py b/tests/v1/extras/test_full_text_ocr.py index 17bd9db2..7f1b717a 100644 --- a/tests/v1/extras/test_full_text_ocr.py +++ b/tests/v1/extras/test_full_text_ocr.py @@ -6,16 +6,6 @@ from mindee.v1.product.international_id import InternationalIdV2 from tests.utils import V1_EXTRAS_DIR -# NOTE: Implementing extras per pages without content (like the Java library) -# would be a breaking change for the Python SDK. -# This fixture is left here as a reminder that next major version should probably implement it. - -# @pytest.fixture -# def load_pages(): -# with open(EXTRAS_DIR / "full_text_ocr/complete.json", "r") as file: -# prediction_data = json.load(file) -# return AsyncPredictResponse(InternationalIdV2, prediction_data).document.inference.pages - @pytest.fixture def load_document(): @@ -25,14 +15,9 @@ def load_document(): return AsyncPredictResponse(InternationalIdV2, prediction_data).document -def test_get_full_text_ocr_result( - load_document, - # load_pages -): +def test_get_full_text_ocr_result(load_document): expected_text = (V1_EXTRAS_DIR / "full_text_ocr/full_text_ocr.txt").read_text() full_text_ocr = load_document.extras.full_text_ocr - # page0_ocr = load_pages[0].extras.full_text_ocr.content assert expected_text.strip() == str(full_text_ocr) - # assert "\n".join(expected_text) == page0_ocr diff --git a/tests/v2/test_cli.py b/tests/v2/test_cli.py index 13ee6693..cb234b95 100644 --- a/tests/v2/test_cli.py +++ b/tests/v2/test_cli.py @@ -57,7 +57,7 @@ def test_top_level_subcommands_registered(parser: MindeeParser): def test_extraction_command_exposes_full_flag_set(parser: MindeeParser): """Extraction must expose --rag, --raw-text, --confidence, --polygon, --text-context.""" - ns = parser.parser.parse_args( + parsed_args = parser.parser.parse_args( [ "extraction", "--api-key", @@ -77,22 +77,22 @@ def test_extraction_command_exposes_full_flag_set(parser: MindeeParser): "path/to/file.pdf", ] ) - assert ns.cmd == "extraction" - assert ns.api_key == "dummy" - assert ns.model_id == "model-1" - assert ns.alias == "my-alias" - assert ns.rag is True - assert ns.raw_text is True - assert ns.confidence is True - assert ns.polygon is True - assert ns.text_context == "ctx" - assert ns.output == OutputType.FULL.value - assert ns.path == "path/to/file.pdf" + assert parsed_args.cmd == "extraction" + assert parsed_args.api_key == "dummy" + assert parsed_args.model_id == "model-1" + assert parsed_args.alias == "my-alias" + assert parsed_args.rag is True + assert parsed_args.raw_text is True + assert parsed_args.confidence is True + assert parsed_args.polygon is True + assert parsed_args.text_context == "ctx" + assert parsed_args.output == OutputType.FULL.value + assert parsed_args.path == "path/to/file.pdf" def test_extraction_short_flags(parser: MindeeParser): """Extraction must accept the short form of every documented flag.""" - ns = parser.parser.parse_args( + parsed_args = parser.parser.parse_args( [ "extraction", "-k", @@ -112,12 +112,12 @@ def test_extraction_short_flags(parser: MindeeParser): "path/to/file.pdf", ] ) - assert ns.rag is True - assert ns.raw_text is True - assert ns.confidence is True - assert ns.polygon is True - assert ns.text_context == "ctx" - assert ns.output == OutputType.RAW.value + assert parsed_args.rag is True + assert parsed_args.raw_text is True + assert parsed_args.confidence is True + assert parsed_args.polygon is True + assert parsed_args.text_context == "ctx" + assert parsed_args.output == OutputType.RAW.value @pytest.mark.parametrize( @@ -135,7 +135,7 @@ def test_non_extraction_commands_omit_extraction_only_flags( def test_search_models_flags(parser: MindeeParser): - ns = parser.parser.parse_args( + parsed_args = parser.parser.parse_args( [ "search-models", "--api-key", @@ -147,11 +147,11 @@ def test_search_models_flags(parser: MindeeParser): "--raw-json", ] ) - assert ns.cmd == "search-models" - assert ns.api_key == "dummy" - assert ns.name == "invoice" - assert ns.model_type == "extraction" - assert ns.raw_json is True + assert parsed_args.cmd == "search-models" + assert parsed_args.api_key == "dummy" + assert parsed_args.name == "invoice" + assert parsed_args.model_type == "extraction" + assert parsed_args.raw_json is True def test_search_models_rejects_invalid_model_type(parser: MindeeParser): @@ -160,7 +160,7 @@ def test_search_models_rejects_invalid_model_type(parser: MindeeParser): def test_search_rag_documents_flags(parser: MindeeParser): - ns = parser.parser.parse_args( + parsed_args = parser.parser.parse_args( [ "search-rag-docs", "--api-key", @@ -172,16 +172,16 @@ def test_search_rag_documents_flags(parser: MindeeParser): "--raw-json", ] ) - assert ns.cmd == "search-rag-docs" - assert ns.api_key == "dummy" - assert ns.model_id == "model-1" - assert ns.filename == "invoice" - assert ns.raw_json is True + assert parsed_args.cmd == "search-rag-docs" + assert parsed_args.api_key == "dummy" + assert parsed_args.model_id == "model-1" + assert parsed_args.filename == "invoice" + assert parsed_args.raw_json is True def test_v1_group_dispatches_to_v1_product(parser: MindeeParser): """The `v1` group preserves the existing V1 product subcommand shape.""" - ns = parser.parser.parse_args( + parsed_args = parser.parser.parse_args( [ "v1", "invoice", @@ -197,10 +197,10 @@ def test_v1_group_dispatches_to_v1_product(parser: MindeeParser): ), ] ) - assert ns.cmd == "v1" - assert ns.product_name == "invoice" - assert ns.api_key == "dummy" - assert ns.output_type == "summary" + assert parsed_args.cmd == "v1" + assert parsed_args.product_name == "invoice" + assert parsed_args.api_key == "dummy" + assert parsed_args.output_type == "summary" def test_extraction_dispatches_to_inference_command(monkeypatch, parser: MindeeParser): @@ -283,13 +283,7 @@ def call_parse(self): def test_each_inference_command_is_self_contained(parser: MindeeParser): - """Every V2 inference command is its own subclass of BaseInferenceCommand. - - Locks in the per-product class architecture: the dispatcher must hold - distinct ``BaseInferenceCommand`` instances rather than a single - config-driven command, so future products that don't fit the - document-extraction shape can simply not extend this base. - """ + """Every V2 inference command is its own subclass of BaseInferenceCommand.""" expected = { "classification": ClassificationCommand, "crop": CropCommand,