diff --git a/CHANGELOG.md b/CHANGELOG.md index f8e2fe09..cb456cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Mindee Python API Library Changelog +## v3.13.2 - 2023-10-18 +### Changes +* :arrow_up: update `urllib` & `pillow` dependencies + + ## v3.13.1 - 2023-10-03 ### Changes * :arrow_up: update `urllib`, `charset-normalizer` & `package` dependencies diff --git a/docs/parsing/common.rst b/docs/parsing/common.rst index 5009afbd..1ddd7b39 100644 --- a/docs/parsing/common.rst +++ b/docs/parsing/common.rst @@ -56,6 +56,15 @@ Job :members: +Miscellaneous Parsing +===================== + +FeedbackResponse +---------------- +.. autoclass:: mindee.parsing.common.feedback_response.FeedbackResponse + :members: + + OCR Extraction ============== diff --git a/mindee/__init__.py b/mindee/__init__.py index af37bd29..bf129c3f 100644 --- a/mindee/__init__.py +++ b/mindee/__init__.py @@ -2,4 +2,5 @@ from mindee.client import Client, PageOptions from mindee.parsing.common.api_response import ApiResponse from mindee.parsing.common.async_predict_response import AsyncPredictResponse, Job +from mindee.parsing.common.feedback_response import FeedbackResponse from mindee.parsing.common.predict_response import PredictResponse diff --git a/mindee/cli.py b/mindee/cli.py index 106cd833..6ba82d91 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -9,8 +9,10 @@ from mindee.input.sources import LocalInputSource, UrlInputSource from mindee.parsing.common.async_predict_response import AsyncPredictResponse from mindee.parsing.common.document import Document, serialize_for_json +from mindee.parsing.common.feedback_response import FeedbackResponse from mindee.parsing.common.inference import Inference, TypeInference from mindee.parsing.common.predict_response import PredictResponse +from mindee.parsing.common.string_dict import StringDict @dataclass @@ -142,43 +144,59 @@ class MindeeParser: input_doc: Union[LocalInputSource, UrlInputSource] """Document to be parsed.""" product_class: Type[Inference] - """Product to parse""" + """Product to parse.""" + feedback: Optional[StringDict] + """Dict representation of a feedback.""" def __init__( self, parser: Optional[ArgumentParser] = None, parsed_args: Optional[Namespace] = None, client: Optional[Client] = None, - input_doc: Optional[Union[LocalInputSource, UrlInputSource]] = None, document_info: Optional[CommandConfig] = None, ) -> None: self.parser = parser if parser else ArgumentParser(description="Mindee_API") self.parsed_args = parsed_args if parsed_args else self._set_args() - self.client = client if client else Client(api_key=self.parsed_args.api_key) - # if self.parsed_args.parse_type == "parse": - self.input_doc = input_doc if input_doc else self._get_input_doc() + self.client = ( + client + if client + else Client( + api_key=self.parsed_args.api_key + if "api_key" in self.parsed_args + else None + ) + ) + self._set_input() self.document_info = ( document_info if document_info else DOCUMENTS[self.parsed_args.product_name] ) def call_endpoint(self) -> None: """Calls the proper type of endpoint according to given command.""" - # if self.parsed_args.parse_type == "parse": - self.call_parse() - - # else: - # self.call_fetch() - - # def call_fetch(self) -> None: - # """Fetches an API's for a previously enqueued document.""" - # response: AsyncPredictResponse = self._parse_queued() - # if self.parsed_args.output_type == "raw": - # print(response.raw_http) - # else: - # if not hasattr(response, "document") or response.document is None: - # print(response.job) - # else: - # print(response.document) + if self.parsed_args.parse_type == "parse": + self.call_parse() + else: + self.call_feedback() + + def call_feedback(self) -> None: + """Sends feedback to an API.""" + custom_endpoint: Optional[Endpoint] = None + if self.parsed_args.product_name == "custom": + custom_endpoint = self.client.create_endpoint( + self.parsed_args.endpoint_name, + self.parsed_args.account_name, + self.parsed_args.api_version, + ) + if self.feedback is None: + raise RuntimeError("Invalid feedback provided.") + + response: FeedbackResponse = self.client.send_feedback( + self.document_info.doc_class, + self.parsed_args.document_id, + {"feedback": self.feedback}, + custom_endpoint, + ) + print(json.dumps(response.feedback, indent=2)) def call_parse(self) -> None: """Calls an endpoint with the appropriate method, and displays the results.""" @@ -251,21 +269,6 @@ def _parse_async(self) -> AsyncPredictResponse: endpoint=custom_endpoint, ) - # def _parse_queued(self) -> AsyncPredictResponse: - # """Fetches a queue's result from a document's id.""" - # custom_endpoint: Optional[Endpoint] = None - # if self.parsed_args.product_name == "custom": - # self.client.create_endpoint( - # self.parsed_args.endpoint_name, - # self.parsed_args.account_name, - # self.parsed_args.api_version, - # ) - # return self.client.parse_queued( - # self.document_info.doc_class, - # self.parsed_args.queue_id, - # custom_endpoint, - # ) - def _doc_str(self, output_type: str, doc_response: Document) -> str: if output_type == "parsed": return json.dumps(doc_response, indent=2, default=serialize_for_json) @@ -273,29 +276,20 @@ def _doc_str(self, output_type: str, doc_response: Document) -> str: def _set_args(self) -> Namespace: """Parse command line arguments.""" - # call_parser = self.parser.add_subparsers( - # dest="parse_type", - # required=True, - # ) - # parse_subparser = call_parser.add_parser("parse") - # fetch_subparser = call_parser.add_parser("fetch") - - # parse_product_subparsers = parse_subparser.add_subparsers( - # dest="product_name", - # required=True, - # ) parse_product_subparsers = self.parser.add_subparsers( dest="product_name", required=True, ) - # fetch_product_subparsers = fetch_subparser.add_subparsers( - # dest="product_name", - # required=True, - # ) - for name, info in DOCUMENTS.items(): - parse_subp = parse_product_subparsers.add_parser(name, help=info.help) + parse_subparser = parse_product_subparsers.add_parser(name, help=info.help) + + call_parser = parse_subparser.add_subparsers( + dest="parse_type", required=True + ) + parse_subp = call_parser.add_parser("parse") + feedback_subp = call_parser.add_parser("feedback") + self._add_main_options(parse_subp) self._add_sending_options(parse_subp) self._add_display_options(parse_subp) @@ -321,16 +315,18 @@ def _set_args(self) -> Namespace: default=False, ) - # if info.is_async: - # fetch_subp = fetch_product_subparsers.add_parser(name, help=info.help) - # self._add_main_options(fetch_subp) - # self._add_display_options(fetch_subp) - # self._add_fetch_options(fetch_subp) + self._add_main_options(feedback_subp) + self._add_feedback_options(feedback_subp) parsed_args = self.parser.parse_args() return parsed_args def _add_main_options(self, parser: ArgumentParser) -> None: + """ + Adds main options for most parsings. + + :param parser: current parser. + """ parser.add_argument( "-k", "--key", @@ -341,7 +337,11 @@ def _add_main_options(self, parser: ArgumentParser) -> None: ) def _add_display_options(self, parser: ArgumentParser) -> None: - """Adds options related to output/display of a document (parse, parse-queued).""" + """ + Adds options related to output/display of a document (parse, parse-queued). + + :param parser: current parser. + """ parser.add_argument( "-o", "--output-type", @@ -355,7 +355,11 @@ def _add_display_options(self, parser: ArgumentParser) -> None: ) def _add_sending_options(self, parser: ArgumentParser) -> None: - """Adds options for sending requests (parse, enqueue).""" + """ + Adds options for sending requests (parse, enqueue). + + :param parser: current parser. + """ parser.add_argument( "-i", "--input-type", @@ -386,15 +390,29 @@ def _add_sending_options(self, parser: ArgumentParser) -> None: ) parser.add_argument(dest="path", help="Full path to the file") - # def _add_fetch_options(self, parser: ArgumentParser): - # """Adds an option to provide the queue ID for an async document.""" - # parser.add_argument( - # dest="queue_id", - # help="Async queue ID for a document (required)", - # ) + def _add_feedback_options(self, parser: ArgumentParser): + """ + Adds the option to give feedback manually. + + :param parser: current parser. + """ + parser.add_argument( + dest="document_id", + help="Mindee UUID of the document.", + type=str, + ) + parser.add_argument( + dest="feedback", + type=json.loads, + help='Feedback JSON string to send, ex \'{"key": "value"}\'.', + ) def _add_custom_options(self, parser: ArgumentParser): - """Adds options to custom-type documents.""" + """ + Adds options to custom-type documents. + + :param parser: current parser. + """ parser.add_argument( "-a", "--account", @@ -418,6 +436,7 @@ def _add_custom_options(self, parser: ArgumentParser): ) def _get_input_doc(self) -> Union[LocalInputSource, UrlInputSource]: + """Loads an input document.""" if self.parsed_args.input_type == "file": with open(self.parsed_args.path, "rb", buffering=30) as file_handle: return self.client.source_from_file(file_handle) @@ -435,6 +454,39 @@ def _get_input_doc(self) -> Union[LocalInputSource, UrlInputSource]: return self.client.source_from_url(self.parsed_args.path) return self.client.source_from_path(self.parsed_args.path) + def _get_feedback_doc(self) -> StringDict: + """Loads a feedback.""" + json_doc: StringDict = {} + if self.parsed_args.input_type == "file": + with open(self.parsed_args.path, "rb", buffering=30) as f_f: + json_doc = json.loads(f_f.read()) + elif self.parsed_args.input_type == "base64": + with open(self.parsed_args.path, "rt", encoding="ascii") as f_b64: + json_doc = json.loads(f_b64.read()) + elif self.parsed_args.input_type == "bytes": + with open(self.parsed_args.path, "rb") as f_b: + json_doc = json.loads(f_b.read()) + else: + if ( + not self.parsed_args.feedback + or not "feedback" in self.parsed_args.feedback + ): + raise RuntimeError("Invalid feedback.") + if not json_doc or "feedback" not in json_doc: + raise RuntimeError("Invalid feedback.") + return json_doc + + def _set_input(self) -> None: + """Loads an input document, or a feedback document.""" + self.feedback = None + if self.parsed_args.parse_type == "feedback": + if not self.parsed_args.feedback: + self.feedback = self._get_feedback_doc() + else: + self.feedback = self.parsed_args.feedback + else: + self.input_doc = self._get_input_doc() + def main() -> None: """Run the Command Line Interface.""" diff --git a/mindee/client.py b/mindee/client.py index 01053ae0..c59343fb 100644 --- a/mindee/client.py +++ b/mindee/client.py @@ -16,8 +16,10 @@ from mindee.mindee_http.error import handle_error from mindee.mindee_http.mindee_api import MindeeApi from mindee.parsing.common.async_predict_response import AsyncPredictResponse +from mindee.parsing.common.feedback_response import FeedbackResponse from mindee.parsing.common.inference import Inference, TypeInference from mindee.parsing.common.predict_response import PredictResponse +from mindee.parsing.common.string_dict import StringDict OTS_OWNER = "mindee" @@ -175,7 +177,7 @@ def parse_queued( :param product_class: The document class to use. The response object will be instantiated based on this parameter. - :param queue_id: queue_id received from the API + :param queue_id: queue_id received from the API. :param endpoint: For custom endpoints, an endpoint has to be given. """ if not endpoint: @@ -275,6 +277,38 @@ def enqueue_and_parse( return poll_results + def send_feedback( + self, + product_class: Type[Inference], + document_id: str, + feedback: StringDict, + endpoint: Optional[Endpoint] = None, + ) -> FeedbackResponse: + """ + Send a feedback for a document. + + :param product_class: The document class to use. + The response object will be instantiated based on this parameter. + + :param document_id: The id of the document to send feedback to. + :param feedback: Feedback to send. + :param endpoint: For custom endpoints, an endpoint has to be given. + """ + if not document_id or len(document_id) == 0: + raise RuntimeError("Invalid document_id.") + if not endpoint: + endpoint = self._initialize_ots_endpoint(product_class) + + feedback_response = endpoint.document_feedback_req_put(document_id, feedback) + if not feedback_response.ok: + raise handle_error( + str(product_class.endpoint_name), + feedback_response.json(), + feedback_response.status_code, + ) + + return FeedbackResponse(feedback_response.json()) + def _make_request( self, product_class: Type[Inference], @@ -360,7 +394,7 @@ def _get_queued_document( return AsyncPredictResponse(product_class, queue_response.json()) - def _initialize_ots_endpoint(self, product_class) -> Endpoint: + def _initialize_ots_endpoint(self, product_class: Type[Inference]) -> Endpoint: if product_class.__name__ == "CustomV1": raise TypeError("Missing endpoint specifications for custom build.") endpoint_info: Dict[str, str] = product_class.get_endpoint_info(product_class) diff --git a/mindee/mindee_http/base_endpoint.py b/mindee/mindee_http/base_endpoint.py new file mode 100644 index 00000000..af4e30ac --- /dev/null +++ b/mindee/mindee_http/base_endpoint.py @@ -0,0 +1,15 @@ +from abc import ABC + +from mindee.mindee_http.mindee_api import MindeeApi + + +class BaseEndpoint(ABC): + """Base endpoint for the Mindee API.""" + + def __init__(self, settings: MindeeApi) -> None: + """ + Base API endpoint class for all endpoints. + + :param settings: Settings relating to all endpoints + """ + self.settings = settings diff --git a/mindee/mindee_http/endpoint.py b/mindee/mindee_http/endpoint.py index a4f9b059..ef1a7ed5 100644 --- a/mindee/mindee_http/endpoint.py +++ b/mindee/mindee_http/endpoint.py @@ -1,12 +1,16 @@ +import json from typing import Union import requests +from requests import Response from mindee.input.sources import LocalInputSource, UrlInputSource +from mindee.mindee_http.base_endpoint import BaseEndpoint from mindee.mindee_http.mindee_api import MindeeApi +from mindee.parsing.common.string_dict import StringDict -class Endpoint: +class Endpoint(BaseEndpoint): """Generic API endpoint for a product.""" def __init__( @@ -19,10 +23,10 @@ def __init__( :param url_name: name of the product as it appears in the URL :param version: interface version """ + super().__init__(settings) self.owner = owner self.url_name = url_name self.version = version - self.settings = settings def predict_req_post( self, @@ -113,21 +117,35 @@ def document_queue_req_get(self, queue_id: str) -> requests.Response: This performs a cropping operation on the server and will increase response time. """ - response = requests.get( + return requests.get( f"{self.settings.url_root}/documents/queue/{queue_id}", headers=self.settings.base_headers, timeout=self.settings.request_timeout, ) - return response - def openapi_get_req(self): + def openapi_get_req(self) -> Response: """Get the OpenAPI specification of the product.""" - response = requests.get( + return requests.get( f"{self.settings.url_root}/openapi.json", headers=self.settings.base_headers, timeout=self.settings.request_timeout, ) - return response + + def document_feedback_req_put( + self, document_id: str, feedback: StringDict + ) -> Response: + """ + Send a feedback. + + :param document_id: ID of the document to send feedback to. + :param feedback: Feedback object to send. + """ + return requests.put( + f"{self.settings.base_url}/documents/{document_id}/feedback", + headers=self.settings.base_headers, + data=json.dumps(feedback, indent=0), + timeout=self.settings.request_timeout, + ) class CustomEndpoint(Endpoint): @@ -177,25 +195,6 @@ def training_async_req_post( ) return response - def document_req_get(self, document_id: str) -> requests.Response: - """ - Make a request to GET annotations for a document. - - :param document_id: ID of the document - """ - params = { - "include_annotations": True, - "include_candidates": True, - "global_orientation": True, - } - response = requests.get( - f"{self.settings.url_root}/documents/{document_id}", - headers=self.settings.base_headers, - params=params, - timeout=self.settings.request_timeout, - ) - return response - def document_req_del(self, document_id: str) -> requests.Response: """ Make a request to DELETE a document. @@ -226,6 +225,25 @@ def documents_req_get(self, page_id: int = 1) -> requests.Response: ) return response + def document_req_get(self, document_id: str) -> requests.Response: + """ + Make a request to GET annotations for a document. + + :param document_id: ID of the document + """ + params = { + "include_annotations": True, + "include_candidates": True, + "global_orientation": True, + } + response = requests.get( + f"{self.settings.url_root}/documents/{document_id}", + headers=self.settings.base_headers, + params=params, + timeout=self.settings.request_timeout, + ) + return response + def annotations_req_post( self, document_id: str, annotations: dict ) -> requests.Response: diff --git a/mindee/mindee_http/mindee_api.py b/mindee/mindee_http/mindee_api.py index db69180c..7f6fbf4d 100644 --- a/mindee/mindee_http/mindee_api.py +++ b/mindee/mindee_http/mindee_api.py @@ -24,6 +24,33 @@ class MindeeApi: api_key: Optional[str] """API Key for the client.""" + base_url: str + request_timeout: int + + def __init__( + self, + api_key: Optional[str], + endpoint_name: str, + account_name: str, + version: str, + ): + self._set_api_key(api_key) + if not self.api_key or len(self.api_key) == 0: + raise RuntimeError( + ( + f"Missing API key for '{endpoint_name} v{version}' (belonging to {account_name})," + " check your Client configuration.\n" + "You can set this using the " + f"'{API_KEY_ENV_NAME}' environment variable." + ) + ) + self.endpoint_name = endpoint_name + self.account_name = account_name + self.version = version + self.request_timeout = TIMEOUT_DEFAULT + self.set_base_url(BASE_URL_DEFAULT) + self.set_from_env() + self.url_root = f"{self.base_url}/products/{self.account_name}/{self.endpoint_name}/v{self.version}" @property def base_headers(self) -> Dict[str, str]: @@ -61,29 +88,3 @@ def set_timeout(self, value: Union[str, int]) -> None: def set_base_url(self, value: str) -> None: """Set the base URL for all requests.""" self.base_url = value - - def __init__( - self, - api_key: Optional[str], - endpoint_name: str, - account_name: str, - version: str, - ): - self._set_api_key(api_key) - if not self.api_key or len(self.api_key) == 0: - raise RuntimeError( - ( - f"Missing API key for '{endpoint_name} v{version}' (belonging to {account_name})," - " check your Client configuration.\n" - "You can set this using the " - f"'{API_KEY_ENV_NAME}' environment variable." - ) - ) - self.endpoint_name = endpoint_name - self.account_name = account_name - self.version = version - self.request_timeout = TIMEOUT_DEFAULT - self.set_base_url(BASE_URL_DEFAULT) - self.set_from_env() - - self.url_root = f"{self.base_url}/products/{self.account_name}/{self.endpoint_name}/v{self.version}" diff --git a/mindee/parsing/common/__init__.py b/mindee/parsing/common/__init__.py index 544fd225..7d5bf8ed 100644 --- a/mindee/parsing/common/__init__.py +++ b/mindee/parsing/common/__init__.py @@ -3,6 +3,7 @@ from mindee.parsing.common.async_predict_response import AsyncPredictResponse from mindee.parsing.common.document import Document from mindee.parsing.common.extras import CropperExtra, Extras +from mindee.parsing.common.feedback_response import FeedbackResponse from mindee.parsing.common.inference import Inference, TypeInference from mindee.parsing.common.job import Job from mindee.parsing.common.ocr.mvision_v1 import MVisionV1 diff --git a/mindee/parsing/common/feedback_response.py b/mindee/parsing/common/feedback_response.py new file mode 100644 index 00000000..54ab67d2 --- /dev/null +++ b/mindee/parsing/common/feedback_response.py @@ -0,0 +1,13 @@ +from mindee.parsing.common.api_response import ApiResponse +from mindee.parsing.common.string_dict import StringDict + + +class FeedbackResponse(ApiResponse): + """Wrapper for feedback response.""" + + feedback: StringDict + + def __init__(self, server_response: StringDict) -> None: + """Feedback endpoint.""" + super().__init__(server_response) + self.feedback = server_response["feedback"] diff --git a/mindee/parsing/common/predict_response.py b/mindee/parsing/common/predict_response.py index 2986e57e..5cc37aaa 100644 --- a/mindee/parsing/common/predict_response.py +++ b/mindee/parsing/common/predict_response.py @@ -1,4 +1,4 @@ -from typing import Generic +from typing import Generic, Type from mindee.parsing.common.api_response import ApiResponse, StringDict from mindee.parsing.common.document import Document @@ -15,7 +15,9 @@ class PredictResponse(Generic[TypeInference], ApiResponse): document: Document """The document object, properly parsed after being retrieved from the server.""" - def __init__(self, prediction_type, raw_response: StringDict) -> None: + def __init__( + self, prediction_type: Type[TypeInference], raw_response: StringDict + ) -> None: """ Container for the raw API response and the parsed document. diff --git a/mindee/version b/mindee/version index 7918c7ab..97c68419 100644 --- a/mindee/version +++ b/mindee/version @@ -1 +1 @@ -3.13.1 \ No newline at end of file +3.13.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 45e156ba..450b04f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ # # pip-compile # + certifi==2023.7.22 # via requests charset-normalizer==3.3.0 @@ -18,11 +19,11 @@ packaging==23.2 # via pikepdf pikepdf==6.2.9 # via mindee (setup.py) -pillow==10.0.1 +pillow==10.1.0 # via pikepdf pytz==2023.3.post1 # via mindee (setup.py) requests==2.31.0 # via mindee (setup.py) -urllib3==2.0.6 +urllib3==2.0.7 # via requests diff --git a/tests/api/test_feedback_response.py b/tests/api/test_feedback_response.py new file mode 100644 index 00000000..cbcef53b --- /dev/null +++ b/tests/api/test_feedback_response.py @@ -0,0 +1,12 @@ +import json + +from mindee.parsing.common.feedback_response import FeedbackResponse + + +def test_empty_feedback_response(): + response = json.load( + open("./tests/data/products/invoices/feedback_response/empty.json") + ) + feedback_response = FeedbackResponse(response) + assert feedback_response is not None + assert feedback_response.feedback["customer_address"] is None diff --git a/tests/api/test_response.py b/tests/api/test_response.py index 3675d580..58ca304b 100644 --- a/tests/api/test_response.py +++ b/tests/api/test_response.py @@ -1,12 +1,7 @@ import json -import pytest - -from mindee.input.sources import PathInput from mindee.parsing.common.predict_response import PredictResponse -from mindee.product import ( # FinancialDocumentV1,; InvoiceV3,; PassportV1,; ReceiptV3, - ReceiptV4, -) +from mindee.product import ReceiptV4 from mindee.product.financial_document.financial_document_v1 import FinancialDocumentV1 from mindee.product.financial_document.financial_document_v1_document import ( FinancialDocumentV1Document, diff --git a/tests/data b/tests/data index 64074190..7817573e 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 6407419004f94f7e3a39e20124ee65c94e561586 +Subproject commit 7817573e455275b41c37faab0c0749d6451cd091 diff --git a/tests/test_cli.py b/tests/test_cli.py index 31e11343..6e0caff2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,9 +1,11 @@ +import json from argparse import Namespace +from sys import api_version import pytest from mindee.cli import MindeeParser -from mindee.mindee_http.error import MindeeHTTPException +from mindee.mindee_http.error import MindeeHTTPClientException, MindeeHTTPException from tests.utils import clear_envvars @@ -22,7 +24,7 @@ def custom_doc(monkeypatch): output_type="summary", include_words=False, path="./tests/data/file_types/pdf/blank.pdf", - # parse_type="parse", + parse_type="parse", async_parse=False, ) @@ -32,13 +34,14 @@ def ots_doc(monkeypatch): clear_envvars(monkeypatch) return Namespace( api_key="dummy", + product_name="invoice", cut_doc=False, doc_pages=3, input_type="path", output_type="summary", include_words=False, path="./tests/data/products/invoices/invoice.pdf", - # parse_type="parse", + parse_type="parse", async_parse=False, ) @@ -48,25 +51,34 @@ def ots_doc_enqueue_and_parse(monkeypatch): clear_envvars(monkeypatch) return Namespace( api_key="dummy", + product_name="invoice-splitter", cut_doc=False, doc_pages=3, input_type="path", include_words=False, path="./tests/data/products/invoice_splitter/default_sample.pdf", - # parse_type="parse", + parse_type="parse", async_parse=True, ) @pytest.fixture -def ots_doc_fetch(monkeypatch): +def ots_doc_feedback(monkeypatch): clear_envvars(monkeypatch) + dummy_feedback = '{"feedback": {"dummy_field": {"value": "dummy"}}}' return Namespace( api_key="dummy", output_type="summary", + product_name="custom", + endpoint_name="dummy-endpoint", + account_name="dummy", + api_version="dummy", queue_id="dummy-queue-id", call_method="parse-queued", - # parse_type="fetch", + input_type="path", + path="./tests/data/file_types/pdf/blank.pdf", + parse_type="feedback", + feedback=json.loads(dummy_feedback), ) @@ -148,13 +160,13 @@ def test_cli_invoice_splitter_enqueue(ots_doc_enqueue_and_parse): parser.call_endpoint() -# def test_cli_invoice_splitter_parse_queued(ots_doc_fetch): -# ots_doc_fetch.product_name = "invoice-splitter" -# ots_doc_fetch.api_key = "" -# with pytest.raises(RuntimeError): -# parser = MindeeParser(parsed_args=ots_doc_fetch) -# parser.call_endpoint() -# ots_doc_fetch.api_key = "dummy" -# with pytest.raises(MindeeHTTPException): -# parser = MindeeParser(parsed_args=ots_doc_fetch) -# parser.call_endpoint() +def test_cli_feedback(ots_doc_feedback): + ots_doc_feedback.document_id = "dummy-document-id" + ots_doc_feedback.api_key = "" + with pytest.raises(RuntimeError): + parser = MindeeParser(parsed_args=ots_doc_feedback) + parser.call_endpoint() + ots_doc_feedback.api_key = "dummy" + with pytest.raises(MindeeHTTPClientException): + parser = MindeeParser(parsed_args=ots_doc_feedback) + parser.call_endpoint() diff --git a/tests/test_client.py b/tests/test_client.py index 1d128e7e..bdc4df8f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -34,6 +34,11 @@ def test_parse_path_without_token(empty_client: Client): empty_client.parse(product.ReceiptV4, input_doc) +def test_feedback_without_id(empty_client: Client): + with pytest.raises(RuntimeError): + empty_client.send_feedback(product.ReceiptV4, "", {}) + + def test_parse_path_with_env_token(env_client: Client): with pytest.raises(MindeeHTTPException): input_doc = env_client.source_from_path(FILE_TYPES_DIR / "pdf" / "blank.pdf")