From f4d94a9f9e2aa666a1333c803f38af5fd2c0c47e Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:43:23 +0200 Subject: [PATCH 1/2] :boom: :recycle: drop requests in favor of httpx --- .pre-commit-config.yaml | 3 +- mindee/input/local_input_source.py | 2 +- mindee/input/url_input_source.py | 10 +-- mindee/mindee_http/response_validation.py | 19 +++-- mindee/v1/mindee_http/endpoint.py | 77 ++++++++++--------- mindee/v1/mindee_http/workflow_endpoint.py | 6 +- mindee/v2/mindee_http/mindee_api_v2.py | 36 ++++----- .../v2/mindee_http/response_validation_v2.py | 12 ++- pyproject.toml | 3 +- tests/v1/api/test_async_response.py | 6 +- tests/v2/test_client.py | 11 +-- 11 files changed, 93 insertions(+), 92 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7264114..9ef3aca5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,8 +35,7 @@ repos: args: [] exclude: "tests/|examples/|docs/" additional_dependencies: - - toml - - types-requests + - httpx - types-setuptools - importlib-metadata - types-Pillow diff --git a/mindee/input/local_input_source.py b/mindee/input/local_input_source.py index 09b19e6b..eeb7731c 100644 --- a/mindee/input/local_input_source.py +++ b/mindee/input/local_input_source.py @@ -109,7 +109,7 @@ def fix_pdf(self, maximum_offset: int = 500) -> None: except MimeTypeError as exc: raise exc except Exception as exc: - print(f"Attempt to fix pdf raised exception {exc}.") + logger.error("Attempt to fix pdf raised exception %s.", exc) raise exc def is_pdf(self) -> bool: diff --git a/mindee/input/url_input_source.py b/mindee/input/url_input_source.py index 1a9e3f8b..1f32baea 100644 --- a/mindee/input/url_input_source.py +++ b/mindee/input/url_input_source.py @@ -5,7 +5,7 @@ from pathlib import Path from urllib.parse import urlparse -import requests +import httpx from mindee.error.mindee_error import MindeeSourceError from mindee.input.bytes_input import BytesInput @@ -153,7 +153,7 @@ def __get_file_extension(filename) -> str | None: :return: The lowercase file extension or None if not found. """ ext = os.path.splitext(filename)[1] - return ext.lower() if ext else None + return str(ext).lower() if ext else None def __fill_filename(self, filename=None) -> str: """ @@ -167,7 +167,7 @@ def __fill_filename(self, filename=None) -> str: if not filename or not os.path.splitext(filename)[1]: filename = self.__generate_file_name( - extension=URLInputSource.__get_file_extension(filename) + extension=URLInputSource.__get_file_extension(filename) or "" ) return filename @@ -185,7 +185,7 @@ def __make_request(url, auth, headers, redirects, max_redirects) -> bytes: :return: The content of the response. :raises MindeeSourceError: If max redirects are exceeded or the request fails. """ - result = requests.get(url, headers=headers, timeout=120, auth=auth) + result = httpx.get(url, headers=headers, timeout=120, auth=auth) if 299 < result.status_code < 400: if redirects == max_redirects: raise MindeeSourceError( @@ -193,7 +193,7 @@ def __make_request(url, auth, headers, redirects, max_redirects) -> bytes: f"aborting operation." ) return URLInputSource.__make_request( - redirects.location, auth, headers, redirects + 1, max_redirects + result.headers["Location"], auth, headers, redirects + 1, max_redirects ) if result.status_code >= 400 or result.status_code < 200: diff --git a/mindee/mindee_http/response_validation.py b/mindee/mindee_http/response_validation.py index 24cb87ce..fbeb423b 100644 --- a/mindee/mindee_http/response_validation.py +++ b/mindee/mindee_http/response_validation.py @@ -1,31 +1,34 @@ import json -import requests +import httpx from mindee.parsing.common import StringDict -def is_valid_sync_response(response: requests.Response) -> bool: +def is_valid_sync_response(response: httpx.Response) -> bool: """ Checks if the synchronous response is valid. Returns True if the response is valid. :param response: a requests response object. :return: bool """ - if not response or not response.ok: + if not response or response.is_error: + return False + try: + response_json = response.json() + except httpx.DecodingError: return False - response_json = json.loads(response.content) # EXTREMELY rare edge case where raw html is sent instead of json. return isinstance(response_json, dict) -def is_valid_async_response(response: requests.Response) -> bool: +def is_valid_async_response(response: httpx.Response) -> bool: """ Checks if the asynchronous response is valid. Also checks if it is a valid synchronous response. Returns True if the response is valid. - :param response: a requests response object. + :param response: an httpx response object. :return: bool """ if not is_valid_sync_response(response): @@ -43,7 +46,7 @@ def is_valid_async_response(response: requests.Response) -> bool: return False -def clean_request_json(response: requests.Response) -> StringDict: +def clean_request_json(response: httpx.Response) -> StringDict: """ Checks and correct the response error format depending on the two possible kind of returns. @@ -51,7 +54,7 @@ def clean_request_json(response: requests.Response) -> StringDict: :return: Returns the job error if the error is due to parsing, returns the http error otherwise. """ response_json = response.json() - if response.status_code < 200 or response.status_code > 302: + if response.is_error: response_json["status_code"] = response.status_code return response_json corrected_json = response_json diff --git a/mindee/v1/mindee_http/endpoint.py b/mindee/v1/mindee_http/endpoint.py index 8348fd94..4d6e0a5d 100644 --- a/mindee/v1/mindee_http/endpoint.py +++ b/mindee/v1/mindee_http/endpoint.py @@ -1,7 +1,4 @@ -import json - -import requests -from requests import Response +import httpx from mindee.input.local_input_source import LocalInputSource from mindee.input.url_input_source import URLInputSource @@ -37,7 +34,7 @@ def predict_req_post( close_file: bool = True, cropper: bool = False, full_text: bool = False, - ) -> requests.Response: + ) -> httpx.Response: """ Make a request to POST a document for prediction. @@ -46,7 +43,7 @@ def predict_req_post( :param close_file: Whether to `close()` the file after parsing it. :param cropper: Including Mindee cropping results. :param full_text: Whether to include the full OCR text response in compatible APIs. - :return: requests response + :return: httpx response """ return self._custom_request( "predict", input_source, include_words, close_file, cropper, full_text @@ -61,7 +58,7 @@ def predict_async_req_post( full_text: bool = False, workflow_id: str | None = None, rag: bool = False, - ) -> requests.Response: + ) -> httpx.Response: """ Make an asynchronous request to POST a document for prediction. @@ -72,7 +69,7 @@ def predict_async_req_post( :param full_text: Whether to include the full OCR text response in compatible APIs. :param workflow_id: Workflow ID. :param rag: If set, will enable Retrieval-Augmented Generation. - :return: requests response + :return: httpx response """ return self._custom_request( "predict_async", @@ -115,7 +112,7 @@ def _custom_request( if isinstance(input_source, URLInputSource): data["document"] = input_source.url - response = requests.post( + response = httpx.post( url=url, headers=self.settings.base_headers, data=data, @@ -124,7 +121,7 @@ def _custom_request( ) else: files = {"document": input_source.read_contents(close_file)} - response = requests.post( + response = httpx.post( url=url, files=files, headers=self.settings.base_headers, @@ -135,39 +132,41 @@ def _custom_request( return response - def document_queue_req_get(self, queue_id: str) -> requests.Response: + def document_queue_req_get(self, queue_id: str) -> httpx.Response: """ Sends a request matching a given queue_id. Returns either a Job or a Document. :param queue_id: queue_id received from the API """ - return requests.get( + return httpx.get( f"{self.settings.url_root}/documents/queue/{queue_id}", headers=self.settings.base_headers, timeout=self.settings.request_timeout, + follow_redirects=True, ) - def openapi_get_req(self) -> Response: + def openapi_get_req(self) -> httpx.Response: """Get the OpenAPI specification of the product.""" - return requests.get( + return httpx.get( f"{self.settings.url_root}/openapi.json", headers=self.settings.base_headers, timeout=self.settings.request_timeout, + follow_redirects=True, ) def document_feedback_req_put( self, document_id: str, feedback: StringDict - ) -> Response: + ) -> httpx.Response: """ Send a feedback. :param document_id: ID of the document to send feedback to. :param feedback: Feedback object to send. """ - return requests.put( + return httpx.put( f"{self.settings.base_url}/v1/documents/{document_id}/feedback", headers=self.settings.base_headers, - data=json.dumps(feedback, indent=0), + data=feedback, timeout=self.settings.request_timeout, ) @@ -177,18 +176,18 @@ class CustomEndpoint(Endpoint): def training_req_post( self, input_source: LocalInputSource, close_file: bool = True - ) -> requests.Response: + ) -> httpx.Response: """ Make a request to POST a document for training. :param input_source: Input object - :return: requests response + :return: httpx response :param close_file: Whether to `close()` the file after parsing it. """ files = {"document": input_source.read_contents(close_file)} params = {"training": True, "with_candidates": True} - response = requests.post( + response = httpx.post( f"{self.settings.url_root}/predict", files=files, headers=self.settings.base_headers, @@ -199,18 +198,18 @@ def training_req_post( def training_async_req_post( self, input_source: LocalInputSource, close_file: bool = True - ) -> requests.Response: + ) -> httpx.Response: """ Make a request to POST a document for training without processing. :param input_source: Input object - :return: requests response + :return: httpx response :param close_file: Whether to `close()` the file after parsing it. """ files = {"document": input_source.read_contents(close_file)} params = {"training": True, "async": True} - response = requests.post( + response = httpx.post( f"{self.settings.url_root}/predict", files=files, headers=self.settings.base_headers, @@ -219,20 +218,20 @@ def training_async_req_post( ) return response - def document_req_del(self, document_id: str) -> requests.Response: + def document_req_del(self, document_id: str) -> httpx.Response: """ Make a request to DELETE a document. :param document_id: ID of the document """ - response = requests.delete( + response = httpx.delete( f"{self.settings.url_root}/documents/{document_id}", headers=self.settings.base_headers, timeout=self.settings.request_timeout, ) return response - def documents_req_get(self, page_id: int = 1) -> requests.Response: + def documents_req_get(self, page_id: int = 1) -> httpx.Response: """ Make a request to GET info on all documents. @@ -241,15 +240,16 @@ def documents_req_get(self, page_id: int = 1) -> requests.Response: params = { "page": page_id, } - response = requests.get( + response = httpx.get( f"{self.settings.url_root}/documents", headers=self.settings.base_headers, params=params, timeout=self.settings.request_timeout, + follow_redirects=True, ) return response - def document_req_get(self, document_id: str) -> requests.Response: + def document_req_get(self, document_id: str) -> httpx.Response: """ Make a request to GET annotations for a document. @@ -260,25 +260,26 @@ def document_req_get(self, document_id: str) -> requests.Response: "include_candidates": True, "global_orientation": True, } - response = requests.get( + response = httpx.get( f"{self.settings.url_root}/documents/{document_id}", headers=self.settings.base_headers, params=params, timeout=self.settings.request_timeout, + follow_redirects=True, ) return response def annotations_req_post( self, document_id: str, annotations: dict - ) -> requests.Response: + ) -> httpx.Response: """ Make a request to POST annotations for a document. :param document_id: ID of the document to annotate :param annotations: Annotations object - :return: requests response + :return: httpx response """ - response = requests.post( + response = httpx.post( f"{self.settings.url_root}/documents/{document_id}/annotations", headers=self.settings.base_headers, json=annotations, @@ -288,15 +289,15 @@ def annotations_req_post( def annotations_req_put( self, document_id: str, annotations: dict - ) -> requests.Response: + ) -> httpx.Response: """ Make a request to PUT annotations for a document. :param document_id: ID of the document to annotate :param annotations: Annotations object - :return: requests response + :return: httpx response """ - response = requests.put( + response = httpx.put( f"{self.settings.url_root}/documents/{document_id}/annotations", headers=self.settings.base_headers, json=annotations, @@ -304,14 +305,14 @@ def annotations_req_put( ) return response - def annotations_req_del(self, document_id: str) -> requests.Response: + def annotations_req_del(self, document_id: str) -> httpx.Response: """ Make a request to DELETE annotations for a document. :param document_id: ID of the document to annotate - :return: requests response + :return: httpx response """ - response = requests.delete( + response = httpx.delete( f"{self.settings.url_root}/documents/{document_id}/annotations", headers=self.settings.base_headers, timeout=self.settings.request_timeout, diff --git a/mindee/v1/mindee_http/workflow_endpoint.py b/mindee/v1/mindee_http/workflow_endpoint.py index 1e139a7b..7217013c 100644 --- a/mindee/v1/mindee_http/workflow_endpoint.py +++ b/mindee/v1/mindee_http/workflow_endpoint.py @@ -1,4 +1,4 @@ -import requests +import httpx from mindee.input.local_input_source import LocalInputSource from mindee.input.url_input_source import URLInputSource @@ -50,7 +50,7 @@ def workflow_execution_post( if isinstance(input_source, URLInputSource): data["document"] = input_source.url - response = requests.post( + response = httpx.post( self.settings.url_root, headers=self.settings.base_headers, data=data, @@ -59,7 +59,7 @@ def workflow_execution_post( ) else: files = {"document": input_source.read_contents(True)} - response = requests.post( + response = httpx.post( self.settings.url_root, files=files, headers=self.settings.base_headers, diff --git a/mindee/v2/mindee_http/mindee_api_v2.py b/mindee/v2/mindee_http/mindee_api_v2.py index ccc39327..8b28aff9 100644 --- a/mindee/v2/mindee_http/mindee_api_v2.py +++ b/mindee/v2/mindee_http/mindee_api_v2.py @@ -1,6 +1,6 @@ import os -import requests +import httpx from mindee.input.local_input_source import LocalInputSource from mindee.input.url_input_source import URLInputSource @@ -85,21 +85,21 @@ def req_post_inference_enqueue( input_source: LocalInputSource | URLInputSource, params: BaseParameters, slug: str, - ) -> requests.Response: + ) -> httpx.Response: """ Make an asynchronous request to POST a document for prediction on the V2 API. :param input_source: Input object. :param params: Options for the enqueueing of the document. :param slug: Slug to use for the enqueueing, defaults to 'inferences'. - :return: requests response. + :return: httpx response. """ data = params.get_form_data() url = f"{self.url_root}/v2/{slug}/enqueue" if isinstance(input_source, LocalInputSource): files = {"file": input_source.read_contents(params.close_file)} - response = requests.post( + response = httpx.post( url=url, files=files, headers=self.base_headers, @@ -108,7 +108,7 @@ def req_post_inference_enqueue( ) elif isinstance(input_source, URLInputSource): data["url"] = input_source.url - response = requests.post( + response = httpx.post( url=url, headers=self.base_headers, data=data, @@ -118,34 +118,34 @@ def req_post_inference_enqueue( raise MindeeAPIV2Error("Invalid input source.") return response - def req_get_job(self, job_id: str) -> requests.Response: + def req_get_job(self, job_id: str) -> httpx.Response: """ Sends a request matching a given queue_id. Returns either a Job or a Document. :param job_id: Job ID, returned by the enqueue request. """ - return requests.get( + return httpx.get( f"{self.url_root}/v2/jobs/{job_id}", headers=self.base_headers, timeout=self.request_timeout, - allow_redirects=False, + follow_redirects=False, ) - def req_get_inference_by_url(self, url) -> requests.Response: + def req_get_inference_by_url(self, url) -> httpx.Response: """ Sends a request matching a given inference_id. Returns either a Job or a Document. :param url: URL to use for the request. :return: Response object from the request. """ - return requests.get( + return httpx.get( url, headers=self.base_headers, timeout=self.request_timeout, - allow_redirects=False, + follow_redirects=False, ) - def req_get_inference(self, inference_id: str, slug: str) -> requests.Response: + def req_get_inference(self, inference_id: str, slug: str) -> httpx.Response: """ Sends a request matching a given queue_id. Returns either a Job or a Document. @@ -154,16 +154,16 @@ def req_get_inference(self, inference_id: str, slug: str) -> requests.Response: """ url = f"{self.url_root}/v2/{slug}/{inference_id}" - return requests.get( + return httpx.get( url, headers=self.base_headers, timeout=self.request_timeout, - allow_redirects=False, + follow_redirects=False, ) def req_get_search_models( self, model_name: str | None, model_type: str | None - ) -> requests.Response: + ) -> httpx.Response: """ Searches for a list of models matching criteria. :param model_name: Name pattern to search for. @@ -171,7 +171,7 @@ def req_get_search_models( :return: Response object containing search results. """ url = f"{self.url_root}/v2/search/models" - return requests.get( + return httpx.get( url, headers=self.base_headers, params={"name": model_name, "model_type": model_type}, @@ -256,10 +256,10 @@ def get_models(self, name: str | None, model_type: str | None): return SearchResponse(dict_response) @staticmethod - def _response_json(response: requests.Response) -> StringDict: + def _response_json(response: httpx.Response) -> StringDict: try: return response.json() - except ValueError as exc: + except httpx.DecodingError as exc: raise MindeeHTTPUnknownErrorV2( f"HTTP {response.status_code} response is not valid JSON: {response.text}" ) from exc diff --git a/mindee/v2/mindee_http/response_validation_v2.py b/mindee/v2/mindee_http/response_validation_v2.py index 30f3e259..bb115bf1 100644 --- a/mindee/v2/mindee_http/response_validation_v2.py +++ b/mindee/v2/mindee_http/response_validation_v2.py @@ -1,11 +1,9 @@ -import json - -import requests +import httpx from mindee.mindee_http import is_valid_sync_response -def is_valid_post_response(response: requests.Response) -> bool: +def is_valid_post_response(response: httpx.Response) -> bool: """ Checks if the POST response is valid and of the expected format. @@ -14,13 +12,13 @@ def is_valid_post_response(response: requests.Response) -> bool: """ if not is_valid_sync_response(response): return False - response_json = json.loads(response.content) + response_json = response.json() if "job" not in response_json: return False return "job" in response_json and not response_json["job"].get("error") -def is_valid_get_response(response: requests.Response) -> bool: +def is_valid_get_response(response: httpx.Response) -> bool: """ Checks if the GET response is valid and of the expected format. @@ -29,7 +27,7 @@ def is_valid_get_response(response: requests.Response) -> bool: """ if not is_valid_sync_response(response): return False - response_json = json.loads(response.content) + response_json = response.json() return ( "inference" in response_json or "job" in response_json diff --git a/pyproject.toml b/pyproject.toml index b4afb752..a427f09d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ requires-python = ">=3.10" dependencies = [ "pypdfium2>=4.0,<6.0", "Pillow>=12.2.0", - "requests>=2.34.2", + "httpx>=0.28.1", ] [project.urls] @@ -44,7 +44,6 @@ Changelog = "https://github.com/mindee/mindee-api-python/blob/main/CHANGELOG.md" lint = [ "pylint==4.0.5", "pre-commit~=4.6.0", - "types-requests>=2.33.0.20260518", "pip-audit>=2.10.0", ] test = [ diff --git a/tests/v1/api/test_async_response.py b/tests/v1/api/test_async_response.py index 8e357704..8830f223 100644 --- a/tests/v1/api/test_async_response.py +++ b/tests/v1/api/test_async_response.py @@ -1,7 +1,7 @@ import json +import httpx import pytest -import requests from mindee.input.path_input import PathInput from mindee.mindee_http.response_validation import is_valid_async_response @@ -20,9 +20,9 @@ FILE_PATH_GET_FAILED_JOB = ASYNC_DIR / "get_failed_job_error.json" -class FakeResponse(requests.Response): +class FakeResponse(httpx.Response): def __init__(self, json_data, _status_code=200): - super().__init__() + super().__init__(status_code=_status_code) self._json_data = json_data self.status_code = _status_code self._ok = True diff --git a/tests/v2/test_client.py b/tests/v2/test_client.py index d013744d..dc4ef3c2 100644 --- a/tests/v2/test_client.py +++ b/tests/v2/test_client.py @@ -1,6 +1,7 @@ import json import os +import httpx import pytest from mindee import ExtractionParameters, ExtractionResponse, LocalResponse @@ -30,7 +31,7 @@ def env_client(monkeypatch) -> Client: def custom_base_url_client(monkeypatch) -> Client: class _FakePostRespError: status_code = 400 # any non-2xx will do - ok = False + is_error = True def json(self): # Shape must match what handle_error_v2 expects @@ -43,7 +44,7 @@ def json(self): class _FakeOkProcessingJobResp: status_code = 200 - ok = True + is_error = False def json(self): data_file = V2_DATA_DIR / "job" / "ok_processing.json" @@ -59,7 +60,7 @@ def content(self) -> bytes: class _FakeOkGetInferenceResp: status_code = 200 - ok = True + is_error = False def json(self): data_file = ( @@ -213,11 +214,11 @@ def test_error_handling(custom_base_url_client): def test_error_handling_non_json_response(env_client, monkeypatch): class _FakeHtmlRespError: status_code = 502 - ok = False + is_error = True text = "502 Bad Gateway" def json(self): - raise ValueError("Expecting value") + raise httpx.DecodingError("Expecting value") def _fake_error_post_inference_enqueue(*args, **kwargs): return _FakeHtmlRespError() From 4dca7f99730248219a21b52542eed517d06a5aa1 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:09:17 +0200 Subject: [PATCH 2/2] fix typos & bump dependency --- mindee/image/extracted_image.py | 10 +++++----- mindee/input/local_input_source.py | 14 +++++++------- mindee/input/local_response.py | 12 ++++++------ mindee/pdf/extracted_pdf.py | 4 ++-- mindee/v1/client.py | 4 ++-- mindee/v1/parsing/custom/line_items.py | 4 ++-- mindee/v2/mindee_http/mindee_api_v2.py | 9 +++------ pyproject.toml | 2 +- tests/v1/mindee_http/test_error.py | 20 ++++++++++---------- tests/v2/test_client_integration.py | 12 ++++++------ 10 files changed, 44 insertions(+), 47 deletions(-) diff --git a/mindee/image/extracted_image.py b/mindee/image/extracted_image.py index b315695e..db76771d 100644 --- a/mindee/image/extracted_image.py +++ b/mindee/image/extracted_image.py @@ -64,11 +64,11 @@ def save_to_file(self, output_path: Path | str, file_format: str | None = None): else: image.save(resolved_path) logger.info("File saved successfully to '%s'.", resolved_path) - except TypeError as exc: - raise MindeeError("Invalid path/filename provided.") from exc - except Exception as exc: - print(exc) - raise MindeeError(f"Could not save file {Path(output_path).name}.") from exc + except TypeError as e: + raise MindeeError("Invalid path/filename provided.") from e + except Exception as e: + print(e) + raise MindeeError(f"Could not save file {Path(output_path).name}.") from e def as_input_source(self) -> FileInput: """ diff --git a/mindee/input/local_input_source.py b/mindee/input/local_input_source.py index eeb7731c..cde4418a 100644 --- a/mindee/input/local_input_source.py +++ b/mindee/input/local_input_source.py @@ -48,9 +48,9 @@ def __init__(self) -> None: try: pdf = pdfium.PdfDocument(self.file_object) self.page_count = len(pdf) - except pdfium.PdfiumError as exc: + except pdfium.PdfiumError as e: logger.warning( - "Could not open PDF file: %s due to %s", self.filename, exc + "Could not open PDF file: %s due to %s", self.filename, e ) self.page_count = 0 self.file_object.seek(0) @@ -106,11 +106,11 @@ def fix_pdf(self, maximum_offset: int = 500) -> None: f"PDF couldn't be fixed. PDF tag was found at position {pos}." ) self.file_mimetype = "application/pdf" - except MimeTypeError as exc: - raise exc - except Exception as exc: - logger.error("Attempt to fix pdf raised exception %s.", exc) - raise exc + except MimeTypeError as e: + raise e + except Exception as e: + logger.error("Attempt to fix pdf raised exception %s.", e) + raise e def is_pdf(self) -> bool: """:return: True if the file is a PDF.""" diff --git a/mindee/input/local_response.py b/mindee/input/local_response.py index 4eaf3115..9eb274ef 100644 --- a/mindee/input/local_response.py +++ b/mindee/input/local_response.py @@ -53,8 +53,8 @@ def as_dict(self) -> dict[str, Any]: try: self._file.seek(0) out_json = json.loads(self._file.read()) - except json.decoder.JSONDecodeError as exc: - raise MindeeError("File is not a valid dictionary.") from exc + except json.decoder.JSONDecodeError as e: + raise MindeeError("File is not a valid dictionary.") from e return out_json @staticmethod @@ -87,8 +87,8 @@ def get_hmac_signature(self, secret_key: str | bytes | bytearray): self._file.read(), algorithm, ) - except (TypeError, ValueError) as exc: - raise MindeeError("Could not get HMAC signature from payload.") from exc + except (TypeError, ValueError) as e: + raise MindeeError("Could not get HMAC signature from payload.") from e return mac.hexdigest() @@ -114,5 +114,5 @@ def deserialize_response(self, response_class: type[ResponseT]) -> ResponseT: """ try: return response_class(self.as_dict) - except KeyError as exc: - raise MindeeError("Invalid class specified for deserialization.") from exc + except KeyError as e: + raise MindeeError("Invalid class specified for deserialization.") from e diff --git a/mindee/pdf/extracted_pdf.py b/mindee/pdf/extracted_pdf.py index 4bbfbb5a..d9d89e78 100644 --- a/mindee/pdf/extracted_pdf.py +++ b/mindee/pdf/extracted_pdf.py @@ -22,10 +22,10 @@ def get_page_count(self) -> int: try: pdf = pdfium.PdfDocument(self.pdf_bytes) return len(pdf) - except Exception as exc: + except Exception as e: raise MindeeError( "Could not retrieve page count from Extracted PDF object." - ) from exc + ) from e def save_to_file(self, output_path: Path | str): """ diff --git a/mindee/v1/client.py b/mindee/v1/client.py index 275c7177..cc500a85 100644 --- a/mindee/v1/client.py +++ b/mindee/v1/client.py @@ -225,8 +225,8 @@ def load_prediction( if local_response.as_dict.get("job"): return AsyncPredictResponse(product_class, local_response.as_dict) return PredictResponse(product_class, local_response.as_dict) - except KeyError as exc: - raise MindeeError("No prediction found in local response.") from exc + except KeyError as e: + raise MindeeError("No prediction found in local response.") from e def parse_queued( self, diff --git a/mindee/v1/parsing/custom/line_items.py b/mindee/v1/parsing/custom/line_items.py index 257a0108..9d1b6faf 100644 --- a/mindee/v1/parsing/custom/line_items.py +++ b/mindee/v1/parsing/custom/line_items.py @@ -96,8 +96,8 @@ def prepare( lines_prepared: list[CustomLine] = [] try: anchor_field: ListField = fields[anchor_name] - except KeyError as exc: - raise MindeeError("No lines have been detected.") from exc + except KeyError as e: + raise MindeeError("No lines have been detected.") from e current_line_number: int = 1 current_line = CustomLine(current_line_number) diff --git a/mindee/v2/mindee_http/mindee_api_v2.py b/mindee/v2/mindee_http/mindee_api_v2.py index 8b28aff9..d2e384b0 100644 --- a/mindee/v2/mindee_http/mindee_api_v2.py +++ b/mindee/v2/mindee_http/mindee_api_v2.py @@ -39,10 +39,7 @@ class MindeeAPIV2(SettingsMixin): api_key: str | None """API Key for the client.""" - def __init__( - self, - api_key: str | None, - ): + def __init__(self, api_key: str | None): self.api_key = ( api_key if api_key @@ -259,7 +256,7 @@ def get_models(self, name: str | None, model_type: str | None): def _response_json(response: httpx.Response) -> StringDict: try: return response.json() - except httpx.DecodingError as exc: + except httpx.DecodingError as e: raise MindeeHTTPUnknownErrorV2( f"HTTP {response.status_code} response is not valid JSON: {response.text}" - ) from exc + ) from e diff --git a/pyproject.toml b/pyproject.toml index a427f09d..795a1390 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ requires-python = ">=3.10" dependencies = [ "pypdfium2>=4.0,<6.0", "Pillow>=12.2.0", - "httpx>=0.28.1", + "httpx>=0.28.1,<1.0", ] [project.urls] diff --git a/tests/v1/mindee_http/test_error.py b/tests/v1/mindee_http/test_error.py index e9d12805..055d4c4f 100644 --- a/tests/v1/mindee_http/test_error.py +++ b/tests/v1/mindee_http/test_error.py @@ -53,8 +53,8 @@ def test_http_enqueue_and_parse_client_error( def test_http_400_error(): - with open(V1_ERROR_DATA_DIR / "error_400_no_details.json") as error_ref: - error_obj = json.load(error_ref) + with open(V1_ERROR_DATA_DIR / "error_400_no_details.json") as e: + error_obj = json.load(e) error_obj["status_code"] = 400 error_400 = handle_error("dummy-url", error_obj) with pytest.raises(MindeeHTTPClientError): @@ -66,8 +66,8 @@ def test_http_400_error(): def test_http_401_error(): - with open(V1_ERROR_DATA_DIR / "error_401_invalid_token.json") as error_ref: - error_obj = json.load(error_ref) + with open(V1_ERROR_DATA_DIR / "error_401_invalid_token.json") as e: + error_obj = json.load(e) error_obj["status_code"] = 401 error_401 = handle_error("dummy-url", error_obj) with pytest.raises(MindeeHTTPClientError): @@ -79,8 +79,8 @@ def test_http_401_error(): def test_http_429_error(): - with open(V1_ERROR_DATA_DIR / "error_429_too_many_requests.json") as error_ref: - error_obj = json.load(error_ref) + with open(V1_ERROR_DATA_DIR / "error_429_too_many_requests.json") as e: + error_obj = json.load(e) error_obj["status_code"] = 429 error_429 = handle_error("dummy-url", error_obj) with pytest.raises(MindeeHTTPClientError): @@ -92,8 +92,8 @@ def test_http_429_error(): def test_http_500_error(): - with open(V1_ERROR_DATA_DIR / "error_500_inference_fail.json") as error_ref: - error_obj = json.load(error_ref) + with open(V1_ERROR_DATA_DIR / "error_500_inference_fail.json") as e: + error_obj = json.load(e) error_obj["status_code"] = 500 error_500 = handle_error("dummy-url", error_obj) with pytest.raises(MindeeHTTPServerError): @@ -105,8 +105,8 @@ def test_http_500_error(): def test_http_500_html_error(): - with open(V1_ERROR_DATA_DIR / "error_50x.html") as error_ref: - error_ref_contents = error_ref.read() + with open(V1_ERROR_DATA_DIR / "error_50x.html") as e: + error_ref_contents = e.read() error_500 = handle_error("dummy-url", error_ref_contents) with pytest.raises(MindeeHTTPServerError): raise error_500 diff --git a/tests/v2/test_client_integration.py b/tests/v2/test_client_integration.py index bd6642d1..ebf88ff0 100644 --- a/tests/v2/test_client_integration.py +++ b/tests/v2/test_client_integration.py @@ -182,10 +182,10 @@ def test_invalid_uuid_must_throw_error(v2_client: Client) -> None: model_id="INVALID MODEL ID", text_context="ignore this message" ) - with pytest.raises(MindeeHTTPErrorV2) as exc_info: + with pytest.raises(MindeeHTTPErrorV2) as e: v2_client.enqueue(input_source, params) - exc: MindeeHTTPErrorV2 = exc_info.value + exc: MindeeHTTPErrorV2 = e.value assert exc.status == 422 assert exc.title is not None assert exc.code.startswith("422-") @@ -203,10 +203,10 @@ def test_unknown_model_must_throw_error(v2_client: Client) -> None: input_source = PathInput(input_path) params = ExtractionParameters(model_id="fc405e37-4ba4-4d03-aeba-533a8d1f0f21") - with pytest.raises(MindeeHTTPErrorV2) as exc_info: + with pytest.raises(MindeeHTTPErrorV2) as e: v2_client.enqueue(input_source, params) - exc: MindeeHTTPErrorV2 = exc_info.value + exc: MindeeHTTPErrorV2 = e.value assert exc.status == 404 assert exc.title is not None assert exc.code.startswith("404-") @@ -236,10 +236,10 @@ def test_unknown_webhook_ids_must_throw_error( confidence=None, ) - with pytest.raises(MindeeHTTPErrorV2) as exc_info: + with pytest.raises(MindeeHTTPErrorV2) as e: v2_client.enqueue(input_source, params) - exc: MindeeHTTPErrorV2 = exc_info.value + exc: MindeeHTTPErrorV2 = e.value assert exc.status == 422 assert exc.title is not None assert exc.code.startswith("422-")