Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/v2/parsing/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ V2 Parsing
./inference/index
./error
./job
./search
36 changes: 36 additions & 0 deletions docs/v2/parsing/search.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---------
V2 Search
---------

Model Webhook
#############
.. autoclass:: mindee.v2.parsing.search.model_webhook.ModelWebhook
:members:
:inherited-members:


Pagination
##########
.. autoclass:: mindee.v2.parsing.search.pagination.Pagination
:members:
:inherited-members:


Search Model
############
.. autoclass:: mindee.v2.parsing.search.search_model.SearchModel
:members:
:inherited-members:

Search Models
#############
.. autoclass:: mindee.v2.parsing.search.search_models.SearchModels
:members:
:inherited-members:

Search Response
###############
.. autoclass:: mindee.v2.parsing.search.search_response.SearchResponse
:members:
:inherited-members:

56 changes: 16 additions & 40 deletions mindee/v2/client.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
from time import sleep
from typing import TypeVar

import requests

from mindee.client_mixin import ClientMixin
from mindee.client_options.polling_options import PollingOptions
from mindee.error.mindee_error import MindeeError
from mindee.input import URLInputSource
from mindee.input.local_input_source import LocalInputSource
from mindee.logger import logger
from mindee.parsing.common import StringDict
from mindee.parsing.common.common_response import CommonStatus
from mindee.v2.client_options.base_parameters import BaseParameters
from mindee.v2.error.mindee_http_error_v2 import (
MindeeHTTPUnknownErrorV2,
handle_error_v2,
)
from mindee.v2.mindee_http.mindee_api_v2 import MindeeAPIV2
from mindee.v2.mindee_http.response_validation_v2 import (
is_valid_get_response,
is_valid_post_response,
)
from mindee.v2.parsing.inference.base_response import BaseResponse
from mindee.v2.parsing.job.job_response import JobResponse
from mindee.v2.parsing.search.search_response import SearchResponse
from mindee.v2.product.extraction.extraction_response import ExtractionResponse

TypeBaseResponse = TypeVar("TypeBaseResponse", bound=BaseResponse)


def _response_json(response: requests.Response) -> StringDict:
try:
return response.json()
except ValueError as exc:
raise MindeeHTTPUnknownErrorV2(
f"HTTP {response.status_code} response is not valid JSON: {response.text}"
) from exc


class Client(ClientMixin):
"""
Mindee API Client.
Expand Down Expand Up @@ -70,14 +51,7 @@ def enqueue(
:return: A valid inference response.
"""
logger.debug("Enqueuing inference using model: %s", params.model_id)
response = self.mindee_api.req_post_inference_enqueue(
input_source=input_source, params=params, slug=params.get_enqueue_slug()
)
dict_response = _response_json(response)

if not is_valid_post_response(response):
handle_error_v2(dict_response)
return JobResponse(dict_response)
return self.mindee_api.enqueue(input_source, params)

def get_job(self, job_id: str) -> JobResponse:
"""
Expand All @@ -90,11 +64,7 @@ def get_job(self, job_id: str) -> JobResponse:
"""
logger.debug("Fetching job: %s", job_id)

response = self.mindee_api.req_get_job(job_id)
dict_response = _response_json(response)
if not is_valid_get_response(response):
handle_error_v2(dict_response)
return JobResponse(dict_response)
return self.mindee_api.get_job(job_id)

def get_result(
self,
Expand All @@ -112,13 +82,7 @@ def get_result(
"""
logger.debug("Fetching result: %s", inference_id)

response = self.mindee_api.req_get_inference(
inference_id, response_type.get_result_slug()
)
dict_response = _response_json(response)
if not is_valid_get_response(response):
handle_error_v2(dict_response)
return response_type(dict_response)
return self.mindee_api.get_result(response_type, inference_id)

def enqueue_and_get_result(
self,
Expand Down Expand Up @@ -176,3 +140,15 @@ def enqueue_and_get_result(
sleep(params.polling_options.delay_sec)

raise MindeeError(f"Couldn't retrieve document after {try_counter + 1} tries.")

def search_models(
self, name: str | None = None, model_type: str | None = None
) -> SearchResponse:
"""
Get a list of models matching the provided name and type.

:param name: Name of the model to filter by.
:param model_type: Type of the model to filter by.
:return: A list of models matching the provided criteria.
"""
return self.mindee_api.get_models(name, model_type)
100 changes: 100 additions & 0 deletions mindee/v2/mindee_http/mindee_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@
from mindee.input.url_input_source import URLInputSource
from mindee.logger import logger
from mindee.mindee_http.settings_mixin import SettingsMixin
from mindee.parsing.common.string_dict import StringDict
from mindee.v1.mindee_http.base_settings import USER_AGENT
from mindee.v2.client_options.base_parameters import BaseParameters
from mindee.v2.error.mindee_api_v2_error import MindeeAPIV2Error
from mindee.v2.error.mindee_http_error_v2 import (
MindeeHTTPUnknownErrorV2,
handle_error_v2,
)
from mindee.v2.mindee_http.response_validation_v2 import (
is_valid_get_response,
is_valid_post_response,
)
from mindee.v2.parsing.job.job_response import JobResponse
from mindee.v2.parsing.search.search_response import SearchResponse

API_KEY_V2_ENV_NAME = "MINDEE_V2_API_KEY"
API_KEY_V2_DEFAULT = ""
Expand Down Expand Up @@ -135,3 +146,92 @@ def req_get_inference(self, inference_id: str, slug: str) -> requests.Response:
timeout=self.request_timeout,
allow_redirects=False,
)

def req_get_search_models(
self, model_name: str | None, model_type: str | None
) -> requests.Response:
"""
Searches for a list of models matching criteria.
:param model_name: Name pattern to search for.
:param model_type: Type of model to search for (exact match).
:return: Response object containing search results.
"""
url = f"{self.url_root}/v2/search/models"
return requests.get(
url,
headers=self.base_headers,
params={"name": model_name, "model_type": model_type},
timeout=self.request_timeout,
)

def enqueue(
self, input_source: LocalInputSource | URLInputSource, params: BaseParameters
) -> JobResponse:
"""
Enqueues a document to a given model.
:param input_source: Input object.
:param params: Parameters
:return: A valid inference Response.
"""

response = self.req_post_inference_enqueue(
input_source=input_source, params=params, slug=params.get_enqueue_slug()
)
dict_response = self._response_json(response)

if not is_valid_post_response(response):
handle_error_v2(dict_response)
return JobResponse(dict_response)

def get_job(self, job_id: str) -> JobResponse:
"""
Get the status of an inference that was previously enqueued.

Can be used for polling.

:param job_id: UUID of the job to retrieve.
:return: A job response.
"""
response = self.req_get_job(job_id)
dict_response = self._response_json(response)
if not is_valid_get_response(response):
handle_error_v2(dict_response)
return JobResponse(dict_response)

def get_result(self, response_type, inference_id: str):
"""
Get the result of an inference that was previously enqueued.

:param response_type: Type of the response to return.
:param inference_id: UUID of the inference to retrieve.
:return: The result of the inference.
"""
response = self.req_get_inference(inference_id, response_type.get_result_slug())
dict_response = self._response_json(response)
if not is_valid_get_response(response):
handle_error_v2(dict_response)
return response_type(dict_response)

def get_models(self, name: str | None, model_type: str | None):
"""
Get a list of models matching the provided name and type.

:param name: Name of the model to filter by.
:param model_type: Type of the model to filter by.
:return: A list of models matching the provided criteria.
"""
logger.debug("Fetching models matching: name=%s and type=%s", name, model_type)
response = self.req_get_search_models(name, model_type)
dict_response = self._response_json(response)
if not is_valid_get_response(response):
handle_error_v2(dict_response)
return SearchResponse(dict_response)

@staticmethod
def _response_json(response: requests.Response) -> StringDict:
try:
return response.json()
except ValueError as exc:
raise MindeeHTTPUnknownErrorV2(
f"HTTP {response.status_code} response is not valid JSON: {response.text}"
) from exc
6 changes: 5 additions & 1 deletion mindee/v2/mindee_http/response_validation_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ def is_valid_get_response(response: requests.Response) -> bool:
if not is_valid_sync_response(response):
return False
response_json = json.loads(response.content)
return "inference" in response_json or "job" in response_json
return (
"inference" in response_json
or "job" in response_json
or "models" in response_json
)
Empty file.
17 changes: 17 additions & 0 deletions mindee/v2/parsing/search/model_webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class ModelWebhook:
"""Model webhook information."""

id: str
"""ID of the webhook."""
name: str
"""Name of the webhook."""
url: str
"""URL of the webhook."""

def __init__(self, server_response: dict) -> None:
self.id = server_response["id"]
self.name = server_response["name"]
self.url = server_response["url"]

def __str__(self) -> str:
return f":Name: {self.name}\n:ID: {self.id}\n:URL: {self.url}"
28 changes: 28 additions & 0 deletions mindee/v2/parsing/search/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Pagination:
"""Pagination metadata."""

per_page: int
"""Number of results per page."""
page: int
"""1-indexed page number."""
total_items: int
"""Total number of 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 (
f":Per Page: {self.per_page}\n"
f":Page: {self.page}\n"
f":Total Items: {self.total_items}\n"
f":Total Pages: {self.total_pages}\n"
)
25 changes: 25 additions & 0 deletions mindee/v2/parsing/search/search_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from mindee.parsing.common.string_dict import StringDict
from mindee.v2.parsing.search.model_webhook import ModelWebhook


class SearchModel:
"""Individual model information."""

id: str
"""Model ID."""
name: str
"""Model name."""
model_type: str
"""Model type."""
webhooks: list[ModelWebhook]
"""Webhooks associated with the model."""

def __init__(self, server_response: StringDict) -> None:
self.id = server_response["id"]
self.name = server_response["name"]
self.model_type = server_response["model_type"]
self.webhooks = (
[ModelWebhook(webhook) for webhook in server_response["webhooks"]]
if "webhooks" in server_response
else []
)
24 changes: 24 additions & 0 deletions mindee/v2/parsing/search/search_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from mindee.v2.parsing.search.search_model import SearchModel


class SearchModels(list[SearchModel]):
"""List of models."""

def __init__(self, raw_response: list[dict]) -> None:
super().__init__([SearchModel(model) for model in raw_response])

def __str__(self) -> str:
"""
Default string representation.
"""
if len(self) == 0:
return "\n"

lines = []
for model in self:
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"
Loading
Loading