From ba08a2ec729d0422d1974f8dcd4cbe9cc9e5ddc4 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 20:24:40 -0700 Subject: [PATCH 01/32] Adding section on edge to User Guide. --- UserGuide.md | 78 +++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index 05794c85..39d0e6a9 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -1,10 +1,10 @@ -# User Guide +# Groundlight User Guide -`groundlight` is a python SDK for working with the Groundlight API. You can send image queries and receive predictions powered by a mixture of machine learning models and human labelers in-the-loop. +Groundlight makes it simple to understand images. You can send image queries and receive predictions powered by a mixture of advanced machine learning models backed up by real people. -*Note: The SDK is currently in "alpha" phase.* +*Note: The SDK is currently in "beta" phase. Interfaces are subject to change.* -## Pre-reqs +## Getting Started 1. Install the `groundlight` sdk. @@ -13,9 +13,9 @@ ``` 1. To access the API, you need an API token. You can create one on the - [groundlight website](https://app.groundlight.ai/reef/my-account/api-tokens). + [groundlight web app](https://app.groundlight.ai/reef/my-account/api-tokens). -1. Use the `Groundlight` client! +1. Set up the `Groundlight` client ```Python from groundlight import Groundlight @@ -24,21 +24,52 @@ The API token should be stored securely - do not commit it to version control! Alternatively, you can use the token by setting the `GROUNDLIGHT_API_TOKEN` environment variable. -## Basics +## Basic Usage #### Create a new detector ```Python -detector = gl.create_detector(name="Dog", query="Is it a dog?") +detector = gl.create_detector(name="door", query="Is the door open?") ``` -#### Retrieve a detector + +## Using Groundlight on the edge + +OFten it is impractical to send every image to the cloud for analysis. Setting up a Groundlight edge environment can help you achieve lower latency and reduce costs. Once you have downloaded and installed your edge model, configure your Groundlight SDK client to use the edge environment by configuring the `endpoint` as such: + + ```Python + from groundlight import Groundlight + gl = Groundlight(endpoint="http://localhost:5717") + ``` + +## Advanced + +### Handling HTTP errors + +If there is an HTTP error during an API call, it will raise an `ApiException`. You can access different metadata from that exception: + +```Python +from groundlight import ApiException, Groundlight + +gl = Groundlight() +try: + detectors = gl.list_detectors() +except ApiException as e: + print(e) + print(e.args) + print(e.body) + print(e.headers) + print(e.reason) + print(e.status) +``` + +### Retrieve an existing detector ```Python detector = gl.get_detector(id="YOUR_DETECTOR_ID") ``` -#### List your detectors +### List your detectors ```Python # Defaults to 10 results per page @@ -48,13 +79,13 @@ detectors = gl.list_detectors() detectors = gl.list_detectors(page=3, page_size=25) ``` -#### Submit an image query +### Submit an image query ```Python image_query = gl.submit_image_query(detector_id="YOUR_DETECTOR_ID", image="path/to/filename.jpeg") ``` -#### Retrieve an image query +### Retrieve an image query In practice, you may want to check for a new result on your query. For example, after a cloud reviewer labels your query. For example, you can use the `image_query.id` after the above `submit_image_query()` call. @@ -62,7 +93,7 @@ In practice, you may want to check for a new result on your query. For example, image_query = gl.get_image_query(id="YOUR_IMAGE_QUERY_ID") ``` -#### List your previous image queries +### List your previous image queries ```Python # Defaults to 10 results per page @@ -71,24 +102,3 @@ image_queries = gl.list_image_queries() # Pagination: 3rd page of 25 results per page image_queries = gl.list_image_queries(page=3, page_size=25) ``` - -## Advanced - -### Handling HTTP errors - -If there is an HTTP error during an API call, it will raise an `ApiException`. You can access different metadata from that exception: - -```Python -from groundlight import ApiException, Groundlight - -gl = Groundlight() -try: - detectors = gl.list_detectors() -except ApiException as e: - print(e) - print(e.args) - print(e.body) - print(e.headers) - print(e.reason) - print(e.status) -``` From 4b378377720897c894de9582052758d6643e0607 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 20:33:29 -0700 Subject: [PATCH 02/32] Adding a partial `get_or_create_detector` method to SDK. --- src/groundlight/client.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index bbaa3fa7..d309e3ff 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -1,6 +1,6 @@ import os from io import BufferedReader, BytesIO -from typing import Union +from typing import Optional, Union from model import Detector, ImageQuery, PaginatedDetectorList, PaginatedImageQueryList from openapi_client import ApiClient, Configuration @@ -61,6 +61,15 @@ def get_detector(self, id: str) -> Detector: obj = self.detectors_api.get_detector(id=id) return Detector.parse_obj(obj.to_dict()) + def get_detector_by_name(self, name: str) -> Optional[Detector]: + #TODO: Do this on server. + detector_list = self.list_detectors(page_size=100) + for d in detector_list.results: + #TODO: Paginate + if d.name == name: + return d + return None + def list_detectors(self, page: int = 1, page_size: int = 10) -> PaginatedDetectorList: obj = self.detectors_api.list_detectors(page=page, page_size=page_size) return PaginatedDetectorList.parse_obj(obj.to_dict()) @@ -69,6 +78,12 @@ def create_detector(self, name: str, query: str, config_name: str = None) -> Det obj = self.detectors_api.create_detector(DetectorCreationInput(name=name, query=query, config_name=config_name)) return Detector.parse_obj(obj.to_dict()) + def get_or_create_detector(self, name: str, query: str, config_name: str = None) -> Detector: + d = self.get_detector_by_name(name) + if d: + return d + return self.create_detector(name, query, config_name) + def get_image_query(self, id: str) -> ImageQuery: obj = self.image_queries_api.get_image_query(id=id) return ImageQuery.parse_obj(obj.to_dict()) From 6f0f7c5765d9ad9602bf7786cb274798d129f44c Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 20:37:29 -0700 Subject: [PATCH 03/32] Doesn't fail silently if pagination would be needed. --- src/groundlight/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index d309e3ff..b91e2831 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -65,9 +65,11 @@ def get_detector_by_name(self, name: str) -> Optional[Detector]: #TODO: Do this on server. detector_list = self.list_detectors(page_size=100) for d in detector_list.results: - #TODO: Paginate if d.name == name: return d + if detector_list.next: + #TODO: paginate + raise RuntimeError("You have too many detectors to use get_detector_by_name") return None def list_detectors(self, page: int = 1, page_size: int = 10) -> PaginatedDetectorList: @@ -79,6 +81,9 @@ def create_detector(self, name: str, query: str, config_name: str = None) -> Det return Detector.parse_obj(obj.to_dict()) def get_or_create_detector(self, name: str, query: str, config_name: str = None) -> Detector: + """Tries to look up the detector by name. If a detector with that name exists, return it. + Otherwise, create a detector with the specified query and config. + """ d = self.get_detector_by_name(name) if d: return d From a06183b1d1808081a76843c4355915704f291f3f Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 21:03:52 -0700 Subject: [PATCH 04/32] submit_image_query can accept a detector or a detector_id --- UserGuide.md | 7 ++++++- src/groundlight/client.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index 39d0e6a9..ddc62e57 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -29,9 +29,14 @@ Groundlight makes it simple to understand images. You can send image queries an #### Create a new detector ```Python -detector = gl.create_detector(name="door", query="Is the door open?") +detector = gl.get_or_create_detector(name="door", query="Is the door open?") ``` +#### Send an image to the detector + +```Python +image_query = gl.submit_image_query(detector=detector, image="path/to/filename.jpeg") +``` ## Using Groundlight on the edge diff --git a/src/groundlight/client.py b/src/groundlight/client.py index b91e2831..54c3f34c 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -97,7 +97,16 @@ def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedIma obj = self.image_queries_api.list_image_queries(page=page, page_size=page_size) return PaginatedImageQueryList.parse_obj(obj.to_dict()) - def submit_image_query(self, detector_id: str, image: Union[str, bytes, BytesIO]) -> ImageQuery: + def submit_image_query(self, + image: Union[str, bytes, BytesIO], + detector: Optional[Detector] = None, + detector_id: Optional[str] = None, + ) -> ImageQuery: + if (detector is not None) and (detector_id): + if detector.id != detector_id: + raise ValueError("You cannot specify both a detector and a different detector_id") + elif (detector is not None): + detector_id = detector.id image_bytesio: Union[BytesIO, BufferedReader] if isinstance(image, str): # Assume it is a filename From f0322d3b08f1e71c263418308d9f7aaffc7ffc7d Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 21:10:48 -0700 Subject: [PATCH 05/32] Consolidating the code samples into a single snippet. --- UserGuide.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index ddc62e57..cfa5f1c1 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -15,27 +15,25 @@ Groundlight makes it simple to understand images. You can send image queries an 1. To access the API, you need an API token. You can create one on the [groundlight web app](https://app.groundlight.ai/reef/my-account/api-tokens). -1. Set up the `Groundlight` client +The API token should be stored securely. Some of the code samples demonstrate including the API token in your source code, which is NOT a best practice. Do not commit your API Token to version control! Instead we recommend setting the `GROUNDLIGHT_API_TOKEN` environment variable. - ```Python - from groundlight import Groundlight - gl = Groundlight(api_token="") - ``` - - The API token should be stored securely - do not commit it to version control! Alternatively, you can use the token by setting the `GROUNDLIGHT_API_TOKEN` environment variable. ## Basic Usage -#### Create a new detector +How to build a simple computer vision system in 5 lines of python: ```Python -detector = gl.get_or_create_detector(name="door", query="Is the door open?") -``` +from groundlight import Groundlight +gl = Groundlight() -#### Send an image to the detector +# Create a new detector: use natural language to describe what you want to understand +detector = gl.create_detector(name="door", query="Is the door open?") -```Python +# Send an image to the detector image_query = gl.submit_image_query(detector=detector, image="path/to/filename.jpeg") + +# Show the results +print(f"The answer is {image_query.result}") ``` ## Using Groundlight on the edge From f221cd10553ae5244835207a1bba38b676258565 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Thu, 6 Oct 2022 21:14:38 -0700 Subject: [PATCH 06/32] format udpates --- UserGuide.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index cfa5f1c1..beb05118 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -2,7 +2,8 @@ Groundlight makes it simple to understand images. You can send image queries and receive predictions powered by a mixture of advanced machine learning models backed up by real people. -*Note: The SDK is currently in "beta" phase. Interfaces are subject to change.* +*Note: The SDK is currently in "beta" phase. Interfaces are subject to change in future versions.* + ## Getting Started @@ -40,10 +41,10 @@ print(f"The answer is {image_query.result}") OFten it is impractical to send every image to the cloud for analysis. Setting up a Groundlight edge environment can help you achieve lower latency and reduce costs. Once you have downloaded and installed your edge model, configure your Groundlight SDK client to use the edge environment by configuring the `endpoint` as such: - ```Python - from groundlight import Groundlight - gl = Groundlight(endpoint="http://localhost:5717") - ``` +```Python +from groundlight import Groundlight +gl = Groundlight(endpoint="http://localhost:5717") +``` ## Advanced From 52e05178ce40e8a7ce53a369fd90f64aa89b1159 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Fri, 7 Oct 2022 08:36:44 -0700 Subject: [PATCH 07/32] Wordsmithing the user guide. --- UserGuide.md | 58 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index beb05118..efaefb37 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -1,27 +1,15 @@ -# Groundlight User Guide +# Groundlight Python SDK -Groundlight makes it simple to understand images. You can send image queries and receive predictions powered by a mixture of advanced machine learning models backed up by real people. +Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. -*Note: The SDK is currently in "beta" phase. Interfaces are subject to change in future versions.* - - -## Getting Started - -1. Install the `groundlight` sdk. - - ```Bash - $ pip install groundlight - ``` +How does it work? Your images are first analyzed by machine learning (ML) models which are automatically trained on your data. If those models have high enough confidence, that's your answer. But if the models are unsure, then the images are progressively escalated to more resource-intensive analysis methods up to real-time human review. So what you get is a computer vision system that starts working right away without even needing to first gather and label a dataset. At first it will operate with high latency, because people need to review the image queries. But over time, the ML systems will learn and improve so queries come back faster with higher confidence. -1. To access the API, you need an API token. You can create one on the - [groundlight web app](https://app.groundlight.ai/reef/my-account/api-tokens). - -The API token should be stored securely. Some of the code samples demonstrate including the API token in your source code, which is NOT a best practice. Do not commit your API Token to version control! Instead we recommend setting the `GROUNDLIGHT_API_TOKEN` environment variable. +*Note: The SDK is currently in "beta" phase. Interfaces are subject to change in future versions.* -## Basic Usage +## Simple Example -How to build a simple computer vision system in 5 lines of python: +How to build a computer vision system in 5 lines of python code: ```Python from groundlight import Groundlight @@ -37,9 +25,35 @@ image_query = gl.submit_image_query(detector=detector, image="path/to/filename.j print(f"The answer is {image_query.result}") ``` + +## Getting Started + +1. Install the `groundlight` sdk. + + ```Bash + $ pip install groundlight + ``` + +1. To access the API, you need an API token. You can create one on the + [groundlight web app](https://app.groundlight.ai/reef/my-account/api-tokens). + +The API token should be stored securely. You can use it directly in your code to initialize the SDK like: + +```python +gl = Groundlight(api_token="") +``` + +which is an easy way to get started, but is NOT a best practice. Please do not commit your API Token to version control! Instead we recommend setting the `GROUNDLIGHT_API_TOKEN` environment variable outside your code so that the SDK can find it automatically. + +```bash +$ export GROUNDLIGHT_API_TOKEN=api_2asdfkjEXAMPLE +$ python glapp.py +``` + + ## Using Groundlight on the edge -OFten it is impractical to send every image to the cloud for analysis. Setting up a Groundlight edge environment can help you achieve lower latency and reduce costs. Once you have downloaded and installed your edge model, configure your Groundlight SDK client to use the edge environment by configuring the `endpoint` as such: +Starting your model evaluations at the edge reduces latency, cost, network bandwidth, and energy. Once you have downloaded and installed your Groundlight edge models, you can configure the Groundlight SDK to use your edge environment by configuring the 'endpoint' to point at your local environment as such: ```Python from groundlight import Groundlight @@ -83,12 +97,6 @@ detectors = gl.list_detectors() detectors = gl.list_detectors(page=3, page_size=25) ``` -### Submit an image query - -```Python -image_query = gl.submit_image_query(detector_id="YOUR_DETECTOR_ID", image="path/to/filename.jpeg") -``` - ### Retrieve an image query In practice, you may want to check for a new result on your query. For example, after a cloud reviewer labels your query. For example, you can use the `image_query.id` after the above `submit_image_query()` call. From e17496df826d9240b42027b769d096f9ef5262a9 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Fri, 7 Oct 2022 10:53:25 -0700 Subject: [PATCH 08/32] Accepts numpy arrays as images into imagequery --- src/groundlight/client.py | 17 +++++++++++++--- src/groundlight/images.py | 13 +++++++++++++ src/groundlight/numpy_optional.py | 32 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/groundlight/numpy_optional.py diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 54c3f34c..a64aa8ec 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -8,7 +8,8 @@ from openapi_client.api.image_queries_api import ImageQueriesApi from openapi_client.model.detector_creation_input import DetectorCreationInput -from groundlight.images import buffer_from_jpeg_file +from groundlight.images import buffer_from_jpeg_file, jpeg_from_numpy +from groundlight.numpy_optional import np API_TOKEN_WEB_URL = "https://app.groundlight.ai/reef/my-account/api-tokens" API_TOKEN_VARIABLE_NAME = "GROUNDLIGHT_API_TOKEN" @@ -98,10 +99,18 @@ def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedIma return PaginatedImageQueryList.parse_obj(obj.to_dict()) def submit_image_query(self, - image: Union[str, bytes, BytesIO], + image: Union[str, bytes, BytesIO, np.ndarray], detector: Optional[Detector] = None, detector_id: Optional[str] = None, ) -> ImageQuery: + """Evaluates an image with Groundlight. + :param image: The image, in several possible formats: + - a filename (string) of a jpeg file + - a byte array or BytesIO with jpeg bytes + - a numpy array in the 0-255 range + :param detector: the Detector object + :param detector_id: the str id of detector like `det_12345` + """ if (detector is not None) and (detector_id): if detector.id != detector_id: raise ValueError("You cannot specify both a detector and a different detector_id") @@ -117,9 +126,11 @@ def submit_image_query(self, elif isinstance(image, BytesIO) or isinstance(image, BufferedReader): # Already in the right format image_bytesio = image + elif isinstance(image, np.ndarray): + image_bytesio = jpeg_from_numpy(image) else: raise TypeError( - "Unsupported type for image. We only support JPEG images specified through a filename, bytes, BytesIO, or BufferedReader object." + "Unsupported type for image. We only support numpy arrays (3,W,H) or JPEG images specified through a filename, bytes, BytesIO, or BufferedReader object." ) obj = self.image_queries_api.submit_image_query(detector_id=detector_id, body=image_bytesio) diff --git a/src/groundlight/images.py b/src/groundlight/images.py index f68d311c..4892d1d0 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -1,6 +1,10 @@ import imghdr import io +from PIL import Image + +from groundlight.numpy_optional import np + def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: """ @@ -12,3 +16,12 @@ def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: return open(image_filename, "rb") else: raise ValueError("We only support JPEG files, for now.") + +def jpeg_from_numpy(img:np.ndarray, jpeg_quality: int = 95) -> bytes: + """Converts a numpy array to jpeg bytes + """ + pilim = Image.fromarray(image.astype('uint8'), 'RGB') + with io.BytesIO() as buf: + pilim.save(buf, "jpeg", quality=jpeg_quality) + out = buf.getvalue() + return out diff --git a/src/groundlight/numpy_optional.py b/src/groundlight/numpy_optional.py new file mode 100644 index 00000000..c3c70adc --- /dev/null +++ b/src/groundlight/numpy_optional.py @@ -0,0 +1,32 @@ +"""A shim that checks if numpy is installed and makes parts of it +available if it is, otherwise fails explicitly. +This can be confusing, but hopefully the errors are explicit enough to be +clear about what's happening, and it makes the code which hopes numpy is installed +look readable. +""" + +class UnavailableModule(object): + def __init__(self, exc: Exception): + self.exc = exc + def __getattr__(self, key): + raise RuntimeError("attempt to use module that failed to load") from self.exc + +class NumpyUnavailable(object): + def __getattr__(self, key): + raise RuntimeError("numpy is not installed") + +try: + import numpy + NUMPY_AVAILABLE = True +except ImportError as e: + numpy = UnavailableModule(e) + NUMPY_AVAILABLE = False + +np = numpy + +__all__ = ['np'] + +if not NUMPY_AVAILABLE: + # Put a few things in the namespace so downstream code looks normal + np.ndarray = NumpyUnavailable() + From ba9e69c05b7f2b406e0e023fbb9443e0a24ede72 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Fri, 7 Oct 2022 17:23:46 -0700 Subject: [PATCH 09/32] Improving interface and docs based on feedback. --- .gitignore | 2 ++ README.md | 32 ++++---------------------------- UserGuide.md | 4 +++- pyproject.toml | 4 ++-- src/groundlight/client.py | 29 +++++++++++++++-------------- src/groundlight/images.py | 8 ++++---- 6 files changed, 30 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index 77648e3e..090759cf 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,5 @@ cython_debug/ poetry.lock node_modules/ + +*.swp diff --git a/README.md b/README.md index 15bca538..8c8e00bb 100644 --- a/README.md +++ b/README.md @@ -12,37 +12,13 @@ $ pip install groundlight $ poetry add groundlight ``` -### Basic Usage +### Usage -To access the API, you need an API token. You can create one on the [groundlight website](https://app.groundlight.ai/reef/my-account/api-tokens). Then, you're ready to use the SDK! - -```Python -from groundlight import Groundlight - -# Load the API client. This defaults to the prod endpoint, -# but you can specify a different endpoint like so: -# gl = Groundlight(endpoint="https://device.integ.groundlight.ai/device-api") -gl = Groundlight(api_token="") - -# Create a detector -detector = gl.create_detector(name="Dog", query="Is it a dog?") - -# (Or, create a detector with a specific named ML config from https://github.com/positronix-ai/zuuul/blob/main/pysrc/predictor_config/binary_classification_predictors.yaml) -# detector = gl.create_detector(name="Dog", query="Is it a dog?", config_name="b4mu11-mlp") - -# Call an API method (e.g., retrieve a list of detectors) -detectors = gl.list_detectors() -``` - -(Alternatively, you can use the token by setting the `GROUNDLIGHT_API_TOKEN` environment variable.) - -### What API methods are available? - -Check out the [User Guide](UserGuide.md)! +For instructions on using the SDK see the public [User Guide](UserGuide.md). For more details, see the [Groundlight](src/groundlight/client.py) -class. This SDK closely follows the methods in our [API -Docs](https://app.groundlight.ai/reef/admin/api-docs). +class. This SDK closely follows the methods in our [API +Docs](https://app.groundlight.ai/reef/admin/public-api-docs/). ## Development diff --git a/UserGuide.md b/UserGuide.md index efaefb37..bc8929fe 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -57,9 +57,11 @@ Starting your model evaluations at the edge reduces latency, cost, network bandw ```Python from groundlight import Groundlight -gl = Groundlight(endpoint="http://localhost:5717") +gl = Groundlight(endpoint="http://localhost:6717") ``` +(Edge model download is not yet generally available.) + ## Advanced ### Handling HTTP errors diff --git a/pyproject.toml b/pyproject.toml index 1a7d422a..f69ae5c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [tool.poetry] name = "groundlight" -version = "0.4.0" +version = "0.5.0" license = "MIT" readme = "UserGuide.md" homepage = "https://groundlight.ai" -description = "Call the Groundlight API from python" +description = "Build computer vision systems from natural language with Groundlight" authors = ["Groundlight AI "] packages = [ { include = "**/*.py", from = "src" }, diff --git a/src/groundlight/client.py b/src/groundlight/client.py index a64aa8ec..be8f3006 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -82,12 +82,16 @@ def create_detector(self, name: str, query: str, config_name: str = None) -> Det return Detector.parse_obj(obj.to_dict()) def get_or_create_detector(self, name: str, query: str, config_name: str = None) -> Detector: - """Tries to look up the detector by name. If a detector with that name exists, return it. + """Tries to look up the detector by name. If a detector with that name and query exists, return it. Otherwise, create a detector with the specified query and config. """ - d = self.get_detector_by_name(name) - if d: - return d + existing_detector = self.get_detector_by_name(name) + if existing_detector: + if existing_detector.query == query: + return existing_detector + else: + raise ValueError(f"Found existing detector with {name=} (id={existing_detector.id}) but the queries don't match") + return self.create_detector(name, query, config_name) def get_image_query(self, id: str) -> ImageQuery: @@ -99,23 +103,20 @@ def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedIma return PaginatedImageQueryList.parse_obj(obj.to_dict()) def submit_image_query(self, - image: Union[str, bytes, BytesIO, np.ndarray], - detector: Optional[Detector] = None, - detector_id: Optional[str] = None, + image: Union[str, bytes, BytesIO, BufferedReader, np.ndarray], + detector: Union[Detector, str], ) -> ImageQuery: """Evaluates an image with Groundlight. :param image: The image, in several possible formats: - a filename (string) of a jpeg file - a byte array or BytesIO with jpeg bytes - - a numpy array in the 0-255 range - :param detector: the Detector object - :param detector_id: the str id of detector like `det_12345` + - a numpy array in the 0-255 range (gets converted to jpeg) + :param detector: the Detector object, or string id of a detector like `det_12345` """ - if (detector is not None) and (detector_id): - if detector.id != detector_id: - raise ValueError("You cannot specify both a detector and a different detector_id") - elif (detector is not None): + if isinstance(detector, Detector): detector_id = detector.id + else: + detector_id = detector image_bytesio: Union[BytesIO, BufferedReader] if isinstance(image, str): # Assume it is a filename diff --git a/src/groundlight/images.py b/src/groundlight/images.py index 4892d1d0..f7ce9568 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -17,11 +17,11 @@ def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: else: raise ValueError("We only support JPEG files, for now.") -def jpeg_from_numpy(img:np.ndarray, jpeg_quality: int = 95) -> bytes: - """Converts a numpy array to jpeg bytes +def jpeg_from_numpy(img:np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: + """Converts a numpy array to BytesIO """ - pilim = Image.fromarray(image.astype('uint8'), 'RGB') + pilim = Image.fromarray(img.astype('uint8'), 'RGB') with io.BytesIO() as buf: pilim.save(buf, "jpeg", quality=jpeg_quality) - out = buf.getvalue() + #out = buf.getvalue() return out From b49a13fdc8c5649fe1aaa3191ca3cbb18e6ea6a3 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 10:29:31 -0800 Subject: [PATCH 10/32] Applying black auto-format by hand. --- generated/model.py | 70 +- generated/openapi_client/api/detectors_api.py | 318 +++------ .../openapi_client/api/image_queries_api.py | 325 +++------ generated/openapi_client/api_client.py | 557 +++++++-------- generated/openapi_client/apis/__init__.py | 1 - generated/openapi_client/configuration.py | 261 +++---- generated/openapi_client/exceptions.py | 17 +- .../model/classification_result.py | 109 +-- generated/openapi_client/model/detector.py | 149 ++-- .../model/detector_creation_input.py | 137 ++-- .../model/detector_type_enum.py | 71 +- generated/openapi_client/model/image_query.py | 174 +++-- .../model/image_query_type_enum.py | 71 +- .../model/paginated_detector_list.py | 119 ++-- .../model/paginated_image_query_list.py | 119 ++-- .../openapi_client/model/result_type_enum.py | 71 +- generated/openapi_client/model_utils.py | 670 ++++++++---------- generated/openapi_client/rest.py | 307 ++++---- generated/setup.py | 6 +- generated/test/test_classification_result.py | 2 +- generated/test/test_detector.py | 5 +- .../test/test_detector_creation_input.py | 2 +- generated/test/test_detector_type_enum.py | 2 +- generated/test/test_detectors_api.py | 14 +- generated/test/test_image_queries_api.py | 14 +- generated/test/test_image_query.py | 9 +- generated/test/test_image_query_type_enum.py | 2 +- .../test/test_paginated_detector_list.py | 5 +- .../test/test_paginated_image_query_list.py | 5 +- generated/test/test_result_type_enum.py | 2 +- src/groundlight/client.py | 19 +- src/groundlight/images.py | 10 +- src/groundlight/numpy_optional.py | 10 +- 33 files changed, 1778 insertions(+), 1875 deletions(-) diff --git a/generated/model.py b/generated/model.py index e2b9cf95..6ff00e78 100644 --- a/generated/model.py +++ b/generated/model.py @@ -14,20 +14,16 @@ class ClassificationResult(BaseModel): confidence: Optional[confloat(ge=0.0, le=1.0)] = Field( None, - description='On a scale of 0 to 1, how confident are we in the predicted label?', + description="On a scale of 0 to 1, how confident are we in the predicted label?", ) - label: str = Field(..., description='What is the predicted label?') + label: str = Field(..., description="What is the predicted label?") class DetectorCreationInput(BaseModel): - name: constr(max_length=200) = Field( - ..., description='A short, descriptive name for the detector.' - ) - query: constr(max_length=300) = Field( - ..., description='A question about the image.' - ) + name: constr(max_length=200) = Field(..., description="A short, descriptive name for the detector.") + query: constr(max_length=300) = Field(..., description="A question about the image.") group_name: Optional[constr(max_length=100)] = Field( - None, description='Which group should this detector be part of?' + None, description="Which group should this detector be part of?" ) confidence_threshold: Optional[confloat(ge=0.0, le=1.0)] = Field( 0.9, @@ -35,33 +31,29 @@ class DetectorCreationInput(BaseModel): ) config_name: Optional[constr(max_length=100)] = Field( None, - description='(Advanced usage) If your account has multiple named ML configuration options enabled, you can use this field to specify which one you would like to use.', + description="(Advanced usage) If your account has multiple named ML configuration options enabled, you can use this field to specify which one you would like to use.", ) class DetectorTypeEnum(Enum): - detector = 'detector' + detector = "detector" class ImageQueryTypeEnum(Enum): - image_query = 'image_query' + image_query = "image_query" class ResultTypeEnum(Enum): - binary_classification = 'binary_classification' + binary_classification = "binary_classification" class Detector(BaseModel): - id: str = Field(..., description='A unique ID for this object.') - type: DetectorTypeEnum = Field(..., description='The type of this object.') - created_at: datetime = Field(..., description='When this detector was created.') - name: constr(max_length=200) = Field( - ..., description='A short, descriptive name for the detector.' - ) - query: str = Field(..., description='A question about the image.') - group_name: str = Field( - ..., description='Which group should this detector be part of?' - ) + id: str = Field(..., description="A unique ID for this object.") + type: DetectorTypeEnum = Field(..., description="The type of this object.") + created_at: datetime = Field(..., description="When this detector was created.") + name: constr(max_length=200) = Field(..., description="A short, descriptive name for the detector.") + query: str = Field(..., description="A question about the image.") + group_name: str = Field(..., description="Which group should this detector be part of?") confidence_threshold: Optional[confloat(ge=0.0, le=1.0)] = Field( 0.9, description="If the detector's prediction is below this confidence threshold, send the image query for human review.", @@ -69,36 +61,24 @@ class Detector(BaseModel): class ImageQuery(BaseModel): - id: str = Field(..., description='A unique ID for this object.') - type: ImageQueryTypeEnum = Field(..., description='The type of this object.') - created_at: datetime = Field(..., description='When was this detector created?') - query: str = Field(..., description='A question about the image.') - detector_id: str = Field( - ..., description='Which detector was used on this image query?' - ) - result_type: ResultTypeEnum = Field( - ..., description='What type of result are we returning?' - ) + id: str = Field(..., description="A unique ID for this object.") + type: ImageQueryTypeEnum = Field(..., description="The type of this object.") + created_at: datetime = Field(..., description="When was this detector created?") + query: str = Field(..., description="A question about the image.") + detector_id: str = Field(..., description="Which detector was used on this image query?") + result_type: ResultTypeEnum = Field(..., description="What type of result are we returning?") result: ClassificationResult class PaginatedDetectorList(BaseModel): count: Optional[int] = Field(None, example=123) - next: Optional[AnyUrl] = Field( - None, example='http://api.example.org/accounts/?page=4' - ) - previous: Optional[AnyUrl] = Field( - None, example='http://api.example.org/accounts/?page=2' - ) + next: Optional[AnyUrl] = Field(None, example="http://api.example.org/accounts/?page=4") + previous: Optional[AnyUrl] = Field(None, example="http://api.example.org/accounts/?page=2") results: Optional[List[Detector]] = None class PaginatedImageQueryList(BaseModel): count: Optional[int] = Field(None, example=123) - next: Optional[AnyUrl] = Field( - None, example='http://api.example.org/accounts/?page=4' - ) - previous: Optional[AnyUrl] = Field( - None, example='http://api.example.org/accounts/?page=2' - ) + next: Optional[AnyUrl] = Field(None, example="http://api.example.org/accounts/?page=4") + previous: Optional[AnyUrl] = Field(None, example="http://api.example.org/accounts/?page=2") results: Optional[List[ImageQuery]] = None diff --git a/generated/openapi_client/api/detectors_api.py b/generated/openapi_client/api/detectors_api.py index 96ad3df9..d3130ea1 100644 --- a/generated/openapi_client/api/detectors_api.py +++ b/generated/openapi_client/api/detectors_api.py @@ -20,7 +20,7 @@ datetime, file_type, none_type, - validate_and_convert_types + validate_and_convert_types, ) from openapi_client.model.detector import Detector from openapi_client.model.detector_creation_input import DetectorCreationInput @@ -40,169 +40,126 @@ def __init__(self, api_client=None): self.api_client = api_client self.create_detector_endpoint = _Endpoint( settings={ - 'response_type': (Detector,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/detectors', - 'operation_id': 'create_detector', - 'http_method': 'POST', - 'servers': None, + "response_type": (Detector,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/detectors", + "operation_id": "create_detector", + "http_method": "POST", + "servers": None, }, params_map={ - 'all': [ - 'detector_creation_input', - ], - 'required': [ - 'detector_creation_input', - ], - 'nullable': [ + "all": [ + "detector_creation_input", ], - 'enum': [ + "required": [ + "detector_creation_input", ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { - }, - 'openapi_types': { - 'detector_creation_input': - (DetectorCreationInput,), - }, - 'attribute_map': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "detector_creation_input": (DetectorCreationInput,), }, - 'location_map': { - 'detector_creation_input': 'body', + "attribute_map": {}, + "location_map": { + "detector_creation_input": "body", }, - 'collection_format_map': { - } + "collection_format_map": {}, }, headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'application/json', - 'application/x-www-form-urlencoded', - 'multipart/form-data' - ] + "accept": ["application/json"], + "content_type": ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"], }, - api_client=api_client + api_client=api_client, ) self.get_detector_endpoint = _Endpoint( settings={ - 'response_type': (Detector,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/detectors/{id}', - 'operation_id': 'get_detector', - 'http_method': 'GET', - 'servers': None, + "response_type": (Detector,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/detectors/{id}", + "operation_id": "get_detector", + "http_method": "GET", + "servers": None, }, params_map={ - 'all': [ - 'id', - ], - 'required': [ - 'id', + "all": [ + "id", ], - 'nullable': [ + "required": [ + "id", ], - 'enum': [ - ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "id": (str,), }, - 'openapi_types': { - 'id': - (str,), + "attribute_map": { + "id": "id", }, - 'attribute_map': { - 'id': 'id', + "location_map": { + "id": "path", }, - 'location_map': { - 'id': 'path', - }, - 'collection_format_map': { - } + "collection_format_map": {}, }, headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [], + "accept": ["application/json"], + "content_type": [], }, - api_client=api_client + api_client=api_client, ) self.list_detectors_endpoint = _Endpoint( settings={ - 'response_type': (PaginatedDetectorList,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/detectors', - 'operation_id': 'list_detectors', - 'http_method': 'GET', - 'servers': None, + "response_type": (PaginatedDetectorList,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/detectors", + "operation_id": "list_detectors", + "http_method": "GET", + "servers": None, }, params_map={ - 'all': [ - 'page', - 'page_size', - ], - 'required': [], - 'nullable': [ - ], - 'enum': [ + "all": [ + "page", + "page_size", ], - 'validation': [ - ] + "required": [], + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "page": (int,), + "page_size": (int,), }, - 'allowed_values': { + "attribute_map": { + "page": "page", + "page_size": "page_size", }, - 'openapi_types': { - 'page': - (int,), - 'page_size': - (int,), + "location_map": { + "page": "query", + "page_size": "query", }, - 'attribute_map': { - 'page': 'page', - 'page_size': 'page_size', - }, - 'location_map': { - 'page': 'query', - 'page_size': 'query', - }, - 'collection_format_map': { - } + "collection_format_map": {}, }, headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [], + "accept": ["application/json"], + "content_type": [], }, - api_client=api_client + api_client=api_client, ) - def create_detector( - self, - detector_creation_input, - **kwargs - ): + def create_detector(self, detector_creation_input, **kwargs): """create_detector # noqa: E501 Create a new detector. # noqa: E501 @@ -248,39 +205,19 @@ def create_detector( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['detector_creation_input'] = \ - detector_creation_input + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["detector_creation_input"] = detector_creation_input return self.create_detector_endpoint.call_with_http_info(**kwargs) - def get_detector( - self, - id, - **kwargs - ): + def get_detector(self, id, **kwargs): """get_detector # noqa: E501 Retrieve a detector by its ID. # noqa: E501 @@ -326,38 +263,19 @@ def get_detector( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['id'] = \ - id + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["id"] = id return self.get_detector_endpoint.call_with_http_info(**kwargs) - def list_detectors( - self, - **kwargs - ): + def list_detectors(self, **kwargs): """list_detectors # noqa: E501 Retrieve a list of detectors. # noqa: E501 @@ -403,29 +321,13 @@ def list_detectors( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") return self.list_detectors_endpoint.call_with_http_info(**kwargs) - diff --git a/generated/openapi_client/api/image_queries_api.py b/generated/openapi_client/api/image_queries_api.py index 6c65ce8d..4926c9c1 100644 --- a/generated/openapi_client/api/image_queries_api.py +++ b/generated/openapi_client/api/image_queries_api.py @@ -20,7 +20,7 @@ datetime, file_type, none_type, - validate_and_convert_types + validate_and_convert_types, ) from openapi_client.model.image_query import ImageQuery from openapi_client.model.paginated_image_query_list import PaginatedImageQueryList @@ -39,172 +39,128 @@ def __init__(self, api_client=None): self.api_client = api_client self.get_image_query_endpoint = _Endpoint( settings={ - 'response_type': (ImageQuery,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/image-queries/{id}', - 'operation_id': 'get_image_query', - 'http_method': 'GET', - 'servers': None, + "response_type": (ImageQuery,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/image-queries/{id}", + "operation_id": "get_image_query", + "http_method": "GET", + "servers": None, }, params_map={ - 'all': [ - 'id', - ], - 'required': [ - 'id', - ], - 'nullable': [ + "all": [ + "id", ], - 'enum': [ + "required": [ + "id", ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "id": (str,), }, - 'allowed_values': { + "attribute_map": { + "id": "id", }, - 'openapi_types': { - 'id': - (str,), + "location_map": { + "id": "path", }, - 'attribute_map': { - 'id': 'id', - }, - 'location_map': { - 'id': 'path', - }, - 'collection_format_map': { - } + "collection_format_map": {}, }, headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [], + "accept": ["application/json"], + "content_type": [], }, - api_client=api_client + api_client=api_client, ) self.list_image_queries_endpoint = _Endpoint( settings={ - 'response_type': (PaginatedImageQueryList,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/image-queries', - 'operation_id': 'list_image_queries', - 'http_method': 'GET', - 'servers': None, + "response_type": (PaginatedImageQueryList,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/image-queries", + "operation_id": "list_image_queries", + "http_method": "GET", + "servers": None, }, params_map={ - 'all': [ - 'page', - 'page_size', - ], - 'required': [], - 'nullable': [ - ], - 'enum': [ + "all": [ + "page", + "page_size", ], - 'validation': [ - ] + "required": [], + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { - }, - 'openapi_types': { - 'page': - (int,), - 'page_size': - (int,), + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "page": (int,), + "page_size": (int,), }, - 'attribute_map': { - 'page': 'page', - 'page_size': 'page_size', + "attribute_map": { + "page": "page", + "page_size": "page_size", }, - 'location_map': { - 'page': 'query', - 'page_size': 'query', + "location_map": { + "page": "query", + "page_size": "query", }, - 'collection_format_map': { - } + "collection_format_map": {}, }, headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [], + "accept": ["application/json"], + "content_type": [], }, - api_client=api_client + api_client=api_client, ) self.submit_image_query_endpoint = _Endpoint( settings={ - 'response_type': (ImageQuery,), - 'auth': [ - 'ApiToken' - ], - 'endpoint_path': '/v1/image-queries', - 'operation_id': 'submit_image_query', - 'http_method': 'POST', - 'servers': None, + "response_type": (ImageQuery,), + "auth": ["ApiToken"], + "endpoint_path": "/v1/image-queries", + "operation_id": "submit_image_query", + "http_method": "POST", + "servers": None, }, params_map={ - 'all': [ - 'detector_id', - 'body', - ], - 'required': [ - 'detector_id', + "all": [ + "detector_id", + "body", ], - 'nullable': [ + "required": [ + "detector_id", ], - 'enum': [ - ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "detector_id": (str,), + "body": (file_type,), }, - 'openapi_types': { - 'detector_id': - (str,), - 'body': - (file_type,), + "attribute_map": { + "detector_id": "detector_id", }, - 'attribute_map': { - 'detector_id': 'detector_id', + "location_map": { + "detector_id": "query", + "body": "body", }, - 'location_map': { - 'detector_id': 'query', - 'body': 'body', - }, - 'collection_format_map': { - } + "collection_format_map": {}, }, - headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'image/jpeg' - ] - }, - api_client=api_client + headers_map={"accept": ["application/json"], "content_type": ["image/jpeg"]}, + api_client=api_client, ) - def get_image_query( - self, - id, - **kwargs - ): + def get_image_query(self, id, **kwargs): """get_image_query # noqa: E501 Retrieve an image-query by its ID. # noqa: E501 @@ -250,38 +206,19 @@ def get_image_query( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['id'] = \ - id + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["id"] = id return self.get_image_query_endpoint.call_with_http_info(**kwargs) - def list_image_queries( - self, - **kwargs - ): + def list_image_queries(self, **kwargs): """list_image_queries # noqa: E501 Retrieve a list of image-queries. # noqa: E501 @@ -327,37 +264,18 @@ def list_image_queries( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") return self.list_image_queries_endpoint.call_with_http_info(**kwargs) - def submit_image_query( - self, - detector_id, - **kwargs - ): + def submit_image_query(self, detector_id, **kwargs): """submit_image_query # noqa: E501 Submit an image query against a detector. You must use `\"Content-Type: image/jpeg\"` for the image data. For example: ```Bash $ curl https://api.groundlight.ai/device-api/v1/image-queries?detector_id=det_abc123 \\ --header \"Content-Type: image/jpeg\" \\ --data-binary @path/to/filename.jpeg ``` # noqa: E501 @@ -404,31 +322,14 @@ def submit_image_query( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['detector_id'] = \ - detector_id + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["detector_id"] = detector_id return self.submit_image_query_endpoint.call_with_http_info(**kwargs) - diff --git a/generated/openapi_client/api_client.py b/generated/openapi_client/api_client.py index 0dd8cdf1..080be755 100644 --- a/generated/openapi_client/api_client.py +++ b/generated/openapi_client/api_client.py @@ -36,7 +36,7 @@ file_type, model_to_dict, none_type, - validate_and_convert_types + validate_and_convert_types, ) @@ -64,8 +64,7 @@ class ApiClient(object): _pool = None - def __init__(self, configuration=None, header_name=None, header_value=None, - cookie=None, pool_threads=1): + def __init__(self, configuration=None, header_name=None, header_value=None, cookie=None, pool_threads=1): if configuration is None: configuration = Configuration.get_default_copy() self.configuration = configuration @@ -77,7 +76,7 @@ def __init__(self, configuration=None, header_name=None, header_value=None, self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/1.0.0/python' + self.user_agent = "OpenAPI-Generator/1.0.0/python" def __enter__(self): return self @@ -90,13 +89,13 @@ def close(self): self._pool.close() self._pool.join() self._pool = None - if hasattr(atexit, 'unregister'): + if hasattr(atexit, "unregister"): atexit.unregister(self.close) @property def pool(self): """Create thread pool on first request - avoids instantiating unused threadpool for blocking clients. + avoids instantiating unused threadpool for blocking clients. """ if self._pool is None: atexit.register(self.close) @@ -106,11 +105,11 @@ def pool(self): @property def user_agent(self): """User agent for this API client""" - return self.default_headers['User-Agent'] + return self.default_headers["User-Agent"] @user_agent.setter def user_agent(self, value): - self.default_headers['User-Agent'] = value + self.default_headers["User-Agent"] = value def set_default_header(self, header_name, header_value): self.default_headers[header_name] = header_value @@ -133,7 +132,7 @@ def __call_api( _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, _host: typing.Optional[str] = None, _check_type: typing.Optional[bool] = None, - _content_type: typing.Optional[str] = None + _content_type: typing.Optional[str] = None, ): config = self.configuration @@ -142,48 +141,39 @@ def __call_api( header_params = header_params or {} header_params.update(self.default_headers) if self.cookie: - header_params['Cookie'] = self.cookie + header_params["Cookie"] = self.cookie if header_params: header_params = self.sanitize_for_serialization(header_params) - header_params = dict(self.parameters_to_tuples(header_params, - collection_formats)) + header_params = dict(self.parameters_to_tuples(header_params, collection_formats)) # path parameters if path_params: path_params = self.sanitize_for_serialization(path_params) - path_params = self.parameters_to_tuples(path_params, - collection_formats) + path_params = self.parameters_to_tuples(path_params, collection_formats) for k, v in path_params: # specified safe chars, encode everything - resource_path = resource_path.replace( - '{%s}' % k, - quote(str(v), safe=config.safe_chars_for_path_param) - ) + resource_path = resource_path.replace("{%s}" % k, quote(str(v), safe=config.safe_chars_for_path_param)) # query parameters if query_params: query_params = self.sanitize_for_serialization(query_params) - query_params = self.parameters_to_tuples(query_params, - collection_formats) + query_params = self.parameters_to_tuples(query_params, collection_formats) # post parameters if post_params or files: post_params = post_params if post_params else [] post_params = self.sanitize_for_serialization(post_params) - post_params = self.parameters_to_tuples(post_params, - collection_formats) + post_params = self.parameters_to_tuples(post_params, collection_formats) post_params.extend(self.files_parameters(files)) - if header_params['Content-Type'].startswith("multipart"): - post_params = self.parameters_to_multipart(post_params, - (dict) ) + if header_params["Content-Type"].startswith("multipart"): + post_params = self.parameters_to_multipart(post_params, (dict)) # body if body: body = self.sanitize_for_serialization(body) # auth setting - self.update_params_for_auth(header_params, query_params, - auth_settings, resource_path, method, body) + self.update_params_for_auth(header_params, query_params, auth_settings, resource_path, method, body) # request url if _host is None: @@ -195,12 +185,17 @@ def __call_api( try: # perform request and return response response_data = self.request( - method, url, query_params=query_params, headers=header_params, - post_params=post_params, body=body, + method, + url, + query_params=query_params, + headers=header_params, + post_params=post_params, + body=body, _preload_content=_preload_content, - _request_timeout=_request_timeout) + _request_timeout=_request_timeout, + ) except ApiException as e: - e.body = e.body.decode('utf-8') + e.body = e.body.decode("utf-8") raise e self.last_response = response_data @@ -208,33 +203,28 @@ def __call_api( return_data = response_data if not _preload_content: - return (return_data) + return return_data return return_data # deserialize response data if response_type: if response_type != (file_type,): encoding = "utf-8" - content_type = response_data.getheader('content-type') + content_type = response_data.getheader("content-type") if content_type is not None: match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) if match: encoding = match.group(1) response_data.data = response_data.data.decode(encoding) - return_data = self.deserialize( - response_data, - response_type, - _check_type - ) + return_data = self.deserialize(response_data, response_type, _check_type) else: return_data = None if _return_http_data_only: - return (return_data) + return return_data else: - return (return_data, response_data.status, - response_data.getheaders()) + return (return_data, response_data.status, response_data.getheaders()) def parameters_to_multipart(self, params, collection_types): """Get parameters as list of tuples, formatting as json if value is collection_types @@ -245,15 +235,15 @@ def parameters_to_multipart(self, params, collection_types): """ new_params = [] if collection_types is None: - collection_types = (dict) + collection_types = dict for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 - if isinstance(v, collection_types): # v is instance of collection_type, formatting as application/json - v = json.dumps(v, ensure_ascii=False).encode("utf-8") - field = RequestField(k, v) - field.make_multipart(content_type="application/json; charset=utf-8") - new_params.append(field) + if isinstance(v, collection_types): # v is instance of collection_type, formatting as application/json + v = json.dumps(v, ensure_ascii=False).encode("utf-8") + field = RequestField(k, v) + field.make_multipart(content_type="application/json; charset=utf-8") + new_params.append(field) else: - new_params.append((k, v)) + new_params.append((k, v)) return new_params @classmethod @@ -271,9 +261,7 @@ def sanitize_for_serialization(cls, obj): :return: The serialized form of data. """ if isinstance(obj, (ModelNormal, ModelComposed)): - return { - key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() - } + return {key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items()} elif isinstance(obj, io.IOBase): return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): @@ -286,7 +274,7 @@ def sanitize_for_serialization(cls, obj): return [cls.sanitize_for_serialization(item) for item in obj] if isinstance(obj, dict): return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()} - raise ApiValueError('Unable to prepare type {} for serialization'.format(obj.__class__.__name__)) + raise ApiValueError("Unable to prepare type {} for serialization".format(obj.__class__.__name__)) def deserialize(self, response, response_type, _check_type): """Deserializes response into an object. @@ -312,8 +300,7 @@ def deserialize(self, response, response_type, _check_type): # save response body into a tmp file and return the instance if response_type == (file_type,): content_disposition = response.getheader("Content-Disposition") - return deserialize_file(response.data, self.configuration, - content_disposition=content_disposition) + return deserialize_file(response.data, self.configuration, content_disposition=content_disposition) # fetch data from response object try: @@ -324,12 +311,7 @@ def deserialize(self, response, response_type, _check_type): # store our data under the key of 'received_data' so users have some # context if they are deserializing a string and the data type is wrong deserialized_data = validate_and_convert_types( - received_data, - response_type, - ['received_data'], - True, - _check_type, - configuration=self.configuration + received_data, response_type, ["received_data"], True, _check_type, configuration=self.configuration ) return deserialized_data @@ -351,7 +333,7 @@ def call_api( _preload_content: bool = True, _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, _host: typing.Optional[str] = None, - _check_type: typing.Optional[bool] = None + _check_type: typing.Optional[bool] = None, ): """Makes the HTTP request (synchronous) and returns deserialized data. @@ -407,87 +389,126 @@ def call_api( then the method will return the response directly. """ if not async_req: - return self.__call_api(resource_path, method, - path_params, query_params, header_params, - body, post_params, files, - response_type, auth_settings, - _return_http_data_only, collection_formats, - _preload_content, _request_timeout, _host, - _check_type) - - return self.pool.apply_async(self.__call_api, (resource_path, - method, path_params, - query_params, - header_params, body, - post_params, files, - response_type, - auth_settings, - _return_http_data_only, - collection_formats, - _preload_content, - _request_timeout, - _host, _check_type)) - - def request(self, method, url, query_params=None, headers=None, - post_params=None, body=None, _preload_content=True, - _request_timeout=None): + return self.__call_api( + resource_path, + method, + path_params, + query_params, + header_params, + body, + post_params, + files, + response_type, + auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, + _request_timeout, + _host, + _check_type, + ) + + return self.pool.apply_async( + self.__call_api, + ( + resource_path, + method, + path_params, + query_params, + header_params, + body, + post_params, + files, + response_type, + auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, + _request_timeout, + _host, + _check_type, + ), + ) + + def request( + self, + method, + url, + query_params=None, + headers=None, + post_params=None, + body=None, + _preload_content=True, + _request_timeout=None, + ): """Makes the HTTP request using RESTClient.""" if method == "GET": - return self.rest_client.GET(url, - query_params=query_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - headers=headers) + return self.rest_client.GET( + url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers, + ) elif method == "HEAD": - return self.rest_client.HEAD(url, - query_params=query_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - headers=headers) + return self.rest_client.HEAD( + url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers, + ) elif method == "OPTIONS": - return self.rest_client.OPTIONS(url, - query_params=query_params, - headers=headers, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) + return self.rest_client.OPTIONS( + url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) elif method == "POST": - return self.rest_client.POST(url, - query_params=query_params, - headers=headers, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) + return self.rest_client.POST( + url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) elif method == "PUT": - return self.rest_client.PUT(url, - query_params=query_params, - headers=headers, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) + return self.rest_client.PUT( + url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) elif method == "PATCH": - return self.rest_client.PATCH(url, - query_params=query_params, - headers=headers, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) + return self.rest_client.PATCH( + url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) elif method == "DELETE": - return self.rest_client.DELETE(url, - query_params=query_params, - headers=headers, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) - else: - raise ApiValueError( - "http method must be `GET`, `HEAD`, `OPTIONS`," - " `POST`, `PATCH`, `PUT` or `DELETE`." + return self.rest_client.DELETE( + url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, ) + else: + raise ApiValueError("http method must be `GET`, `HEAD`, `OPTIONS`," " `POST`, `PATCH`, `PUT` or `DELETE`.") def parameters_to_tuples(self, params, collection_formats): """Get parameters as list of tuples, formatting collections. @@ -502,19 +523,18 @@ def parameters_to_tuples(self, params, collection_formats): for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 if k in collection_formats: collection_format = collection_formats[k] - if collection_format == 'multi': + if collection_format == "multi": new_params.extend((k, value) for value in v) else: - if collection_format == 'ssv': - delimiter = ' ' - elif collection_format == 'tsv': - delimiter = '\t' - elif collection_format == 'pipes': - delimiter = '|' + if collection_format == "ssv": + delimiter = " " + elif collection_format == "tsv": + delimiter = "\t" + elif collection_format == "pipes": + delimiter = "|" else: # csv is the default - delimiter = ',' - new_params.append( - (k, delimiter.join(str(value) for value in v))) + delimiter = "," + new_params.append((k, delimiter.join(str(value) for value in v))) else: new_params.append((k, v)) return new_params @@ -546,15 +566,12 @@ def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[i continue if file_instance.closed is True: raise ApiValueError( - "Cannot read a closed file. The passed in file_type " - "for %s must be open." % param_name + "Cannot read a closed file. The passed in file_type " "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) filedata = self.get_file_data_and_close_file(file_instance) - mimetype = (mimetypes.guess_type(filename)[0] or - 'application/octet-stream') - params.append( - tuple([param_name, tuple([filename, filedata, mimetype])])) + mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream" + params.append(tuple([param_name, tuple([filename, filedata, mimetype])])) return params @@ -569,10 +586,10 @@ def select_header_accept(self, accepts): accepts = [x.lower() for x in accepts] - if 'application/json' in accepts: - return 'application/json' + if "application/json" in accepts: + return "application/json" else: - return ', '.join(accepts) + return ", ".join(accepts) def select_header_content_type(self, content_types, method=None, body=None): """Returns `Content-Type` based on an array of content_types provided. @@ -583,22 +600,19 @@ def select_header_content_type(self, content_types, method=None, body=None): :return: Content-Type (e.g. application/json). """ if not content_types: - return 'application/json' + return "application/json" content_types = [x.lower() for x in content_types] - if (method == 'PATCH' and - 'application/json-patch+json' in content_types and - isinstance(body, list)): - return 'application/json-patch+json' + if method == "PATCH" and "application/json-patch+json" in content_types and isinstance(body, list): + return "application/json-patch+json" - if 'application/json' in content_types or '*/*' in content_types: - return 'application/json' + if "application/json" in content_types or "*/*" in content_types: + return "application/json" else: return content_types[0] - def update_params_for_auth(self, headers, queries, auth_settings, - resource_path, method, body): + def update_params_for_auth(self, headers, queries, auth_settings, resource_path, method, body): """Updates header and query params based on authentication setting. :param headers: Header parameters dict to be updated. @@ -615,22 +629,19 @@ def update_params_for_auth(self, headers, queries, auth_settings, for auth in auth_settings: auth_setting = self.configuration.auth_settings().get(auth) if auth_setting: - if auth_setting['in'] == 'cookie': - headers['Cookie'] = auth_setting['value'] - elif auth_setting['in'] == 'header': - if auth_setting['type'] != 'http-signature': - headers[auth_setting['key']] = auth_setting['value'] - elif auth_setting['in'] == 'query': - queries.append((auth_setting['key'], auth_setting['value'])) + if auth_setting["in"] == "cookie": + headers["Cookie"] = auth_setting["value"] + elif auth_setting["in"] == "header": + if auth_setting["type"] != "http-signature": + headers[auth_setting["key"]] = auth_setting["value"] + elif auth_setting["in"] == "query": + queries.append((auth_setting["key"], auth_setting["value"])) else: - raise ApiValueError( - 'Authentication token must be in `query` or `header`' - ) + raise ApiValueError("Authentication token must be in `query` or `header`") class Endpoint(object): - def __init__(self, settings=None, params_map=None, root_map=None, - headers_map=None, api_client=None, callable=None): + def __init__(self, settings=None, params_map=None, root_map=None, headers_map=None, api_client=None, callable=None): """Creates an endpoint Args: @@ -666,59 +677,54 @@ def __init__(self, settings=None, params_map=None, root_map=None, """ self.settings = settings self.params_map = params_map - self.params_map['all'].extend([ - 'async_req', - '_host_index', - '_preload_content', - '_request_timeout', - '_return_http_data_only', - '_check_input_type', - '_check_return_type', - '_content_type', - '_spec_property_naming' - ]) - self.params_map['nullable'].extend(['_request_timeout']) - self.validations = root_map['validations'] - self.allowed_values = root_map['allowed_values'] - self.openapi_types = root_map['openapi_types'] + self.params_map["all"].extend( + [ + "async_req", + "_host_index", + "_preload_content", + "_request_timeout", + "_return_http_data_only", + "_check_input_type", + "_check_return_type", + "_content_type", + "_spec_property_naming", + ] + ) + self.params_map["nullable"].extend(["_request_timeout"]) + self.validations = root_map["validations"] + self.allowed_values = root_map["allowed_values"] + self.openapi_types = root_map["openapi_types"] extra_types = { - 'async_req': (bool,), - '_host_index': (none_type, int), - '_preload_content': (bool,), - '_request_timeout': (none_type, float, (float,), [float], int, (int,), [int]), - '_return_http_data_only': (bool,), - '_check_input_type': (bool,), - '_check_return_type': (bool,), - '_spec_property_naming': (bool,), - '_content_type': (none_type, str) + "async_req": (bool,), + "_host_index": (none_type, int), + "_preload_content": (bool,), + "_request_timeout": (none_type, float, (float,), [float], int, (int,), [int]), + "_return_http_data_only": (bool,), + "_check_input_type": (bool,), + "_check_return_type": (bool,), + "_spec_property_naming": (bool,), + "_content_type": (none_type, str), } self.openapi_types.update(extra_types) - self.attribute_map = root_map['attribute_map'] - self.location_map = root_map['location_map'] - self.collection_format_map = root_map['collection_format_map'] + self.attribute_map = root_map["attribute_map"] + self.location_map = root_map["location_map"] + self.collection_format_map = root_map["collection_format_map"] self.headers_map = headers_map self.api_client = api_client self.callable = callable def __validate_inputs(self, kwargs): - for param in self.params_map['enum']: + for param in self.params_map["enum"]: if param in kwargs: - check_allowed_values( - self.allowed_values, - (param,), - kwargs[param] - ) + check_allowed_values(self.allowed_values, (param,), kwargs[param]) - for param in self.params_map['validation']: + for param in self.params_map["validation"]: if param in kwargs: check_validations( - self.validations, - (param,), - kwargs[param], - configuration=self.api_client.configuration + self.validations, (param,), kwargs[param], configuration=self.api_client.configuration ) - if kwargs['_check_input_type'] is False: + if kwargs["_check_input_type"] is False: return for key, value in kwargs.items(): @@ -726,52 +732,42 @@ def __validate_inputs(self, kwargs): value, self.openapi_types[key], [key], - kwargs['_spec_property_naming'], - kwargs['_check_input_type'], - configuration=self.api_client.configuration + kwargs["_spec_property_naming"], + kwargs["_check_input_type"], + configuration=self.api_client.configuration, ) kwargs[key] = fixed_val def __gather_params(self, kwargs): - params = { - 'body': None, - 'collection_format': {}, - 'file': {}, - 'form': [], - 'header': {}, - 'path': {}, - 'query': [] - } + params = {"body": None, "collection_format": {}, "file": {}, "form": [], "header": {}, "path": {}, "query": []} for param_name, param_value in kwargs.items(): param_location = self.location_map.get(param_name) if param_location is None: continue if param_location: - if param_location == 'body': - params['body'] = param_value + if param_location == "body": + params["body"] = param_value continue base_name = self.attribute_map[param_name] - if (param_location == 'form' and - self.openapi_types[param_name] == (file_type,)): - params['file'][base_name] = [param_value] - elif (param_location == 'form' and - self.openapi_types[param_name] == ([file_type],)): + if param_location == "form" and self.openapi_types[param_name] == (file_type,): + params["file"][base_name] = [param_value] + elif param_location == "form" and self.openapi_types[param_name] == ([file_type],): # param_value is already a list - params['file'][base_name] = param_value - elif param_location in {'form', 'query'}: + params["file"][base_name] = param_value + elif param_location in {"form", "query"}: param_value_full = (base_name, param_value) params[param_location].append(param_value_full) - if param_location not in {'form', 'query'}: + if param_location not in {"form", "query"}: params[param_location][base_name] = param_value collection_format = self.collection_format_map.get(param_name) if collection_format: - params['collection_format'][base_name] = collection_format + params["collection_format"][base_name] = collection_format return params def __call__(self, *args, **kwargs): - """ This method is invoked when endpoints are called + """This method is invoked when endpoints are called Example: api_instance = DetectorsApi() @@ -786,82 +782,79 @@ def __call__(self, *args, **kwargs): def call_with_http_info(self, **kwargs): try: - index = self.api_client.configuration.server_operation_index.get( - self.settings['operation_id'], self.api_client.configuration.server_index - ) if kwargs['_host_index'] is None else kwargs['_host_index'] + index = ( + self.api_client.configuration.server_operation_index.get( + self.settings["operation_id"], self.api_client.configuration.server_index + ) + if kwargs["_host_index"] is None + else kwargs["_host_index"] + ) server_variables = self.api_client.configuration.server_operation_variables.get( - self.settings['operation_id'], self.api_client.configuration.server_variables + self.settings["operation_id"], self.api_client.configuration.server_variables ) _host = self.api_client.configuration.get_host_from_settings( - index, variables=server_variables, servers=self.settings['servers'] + index, variables=server_variables, servers=self.settings["servers"] ) except IndexError: - if self.settings['servers']: - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" % - len(self.settings['servers']) - ) + if self.settings["servers"]: + raise ApiValueError("Invalid host index. Must be 0 <= index < %s" % len(self.settings["servers"])) _host = None for key, value in kwargs.items(): - if key not in self.params_map['all']: + if key not in self.params_map["all"]: raise ApiTypeError( - "Got an unexpected parameter '%s'" - " to method `%s`" % - (key, self.settings['operation_id']) + "Got an unexpected parameter '%s'" " to method `%s`" % (key, self.settings["operation_id"]) ) # only throw this nullable ApiValueError if _check_input_type # is False, if _check_input_type==True we catch this case # in self.__validate_inputs - if (key not in self.params_map['nullable'] and value is None - and kwargs['_check_input_type'] is False): + if key not in self.params_map["nullable"] and value is None and kwargs["_check_input_type"] is False: raise ApiValueError( "Value may not be None for non-nullable parameter `%s`" - " when calling `%s`" % - (key, self.settings['operation_id']) + " when calling `%s`" % (key, self.settings["operation_id"]) ) - for key in self.params_map['required']: + for key in self.params_map["required"]: if key not in kwargs.keys(): raise ApiValueError( - "Missing the required parameter `%s` when calling " - "`%s`" % (key, self.settings['operation_id']) + "Missing the required parameter `%s` when calling " "`%s`" % (key, self.settings["operation_id"]) ) self.__validate_inputs(kwargs) params = self.__gather_params(kwargs) - accept_headers_list = self.headers_map['accept'] + accept_headers_list = self.headers_map["accept"] if accept_headers_list: - params['header']['Accept'] = self.api_client.select_header_accept( - accept_headers_list) + params["header"]["Accept"] = self.api_client.select_header_accept(accept_headers_list) - if kwargs.get('_content_type'): - params['header']['Content-Type'] = kwargs['_content_type'] + if kwargs.get("_content_type"): + params["header"]["Content-Type"] = kwargs["_content_type"] else: - content_type_headers_list = self.headers_map['content_type'] + content_type_headers_list = self.headers_map["content_type"] if content_type_headers_list: - if params['body'] != "": + if params["body"] != "": header_list = self.api_client.select_header_content_type( - content_type_headers_list, self.settings['http_method'], - params['body']) - params['header']['Content-Type'] = header_list + content_type_headers_list, self.settings["http_method"], params["body"] + ) + params["header"]["Content-Type"] = header_list return self.api_client.call_api( - self.settings['endpoint_path'], self.settings['http_method'], - params['path'], - params['query'], - params['header'], - body=params['body'], - post_params=params['form'], - files=params['file'], - response_type=self.settings['response_type'], - auth_settings=self.settings['auth'], - async_req=kwargs['async_req'], - _check_type=kwargs['_check_return_type'], - _return_http_data_only=kwargs['_return_http_data_only'], - _preload_content=kwargs['_preload_content'], - _request_timeout=kwargs['_request_timeout'], + self.settings["endpoint_path"], + self.settings["http_method"], + params["path"], + params["query"], + params["header"], + body=params["body"], + post_params=params["form"], + files=params["file"], + response_type=self.settings["response_type"], + auth_settings=self.settings["auth"], + async_req=kwargs["async_req"], + _check_type=kwargs["_check_return_type"], + _return_http_data_only=kwargs["_return_http_data_only"], + _preload_content=kwargs["_preload_content"], + _request_timeout=kwargs["_request_timeout"], _host=_host, - collection_formats=params['collection_format']) + collection_formats=params["collection_format"], + ) diff --git a/generated/openapi_client/apis/__init__.py b/generated/openapi_client/apis/__init__.py index 2363ebdf..5a898a73 100644 --- a/generated/openapi_client/apis/__init__.py +++ b/generated/openapi_client/apis/__init__.py @@ -1,4 +1,3 @@ - # flake8: noqa # Import all APIs into this package. diff --git a/generated/openapi_client/configuration.py b/generated/openapi_client/configuration.py index 69fdaec8..d56c8bca 100644 --- a/generated/openapi_client/configuration.py +++ b/generated/openapi_client/configuration.py @@ -20,99 +20,112 @@ JSON_SCHEMA_VALIDATION_KEYWORDS = { - 'multipleOf', 'maximum', 'exclusiveMaximum', - 'minimum', 'exclusiveMinimum', 'maxLength', - 'minLength', 'pattern', 'maxItems', 'minItems' + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + "maxLength", + "minLength", + "pattern", + "maxItems", + "minItems", } + class Configuration(object): """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - Do not edit the class manually. - - :param host: Base url - :param api_key: Dict to store API key(s). - Each entry in the dict specifies an API key. - The dict key is the name of the security scheme in the OAS specification. - The dict value is the API key secret. - :param api_key_prefix: Dict to store API prefix (e.g. Bearer) - The dict key is the name of the security scheme in the OAS specification. - The dict value is an API key prefix when generating the auth data. - :param username: Username for HTTP basic authentication - :param password: Password for HTTP basic authentication - :param discard_unknown_keys: Boolean value indicating whether to discard - unknown properties. A server may send a response that includes additional - properties that are not known by the client in the following scenarios: - 1. The OpenAPI document is incomplete, i.e. it does not match the server - implementation. - 2. The client was generated using an older version of the OpenAPI document - and the server has been upgraded since then. - If a schema in the OpenAPI document defines the additionalProperties attribute, - then all undeclared properties received by the server are injected into the - additional properties map. In that case, there are undeclared properties, and - nothing to discard. - :param disabled_client_side_validations (string): Comma-separated list of - JSON schema validation keywords to disable JSON schema structural validation - rules. The following keywords may be specified: multipleOf, maximum, - exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern, - maxItems, minItems. - By default, the validation is performed for data generated locally by the client - and data received from the server, independent of any validation performed by - the server side. If the input data does not satisfy the JSON schema validation - rules specified in the OpenAPI document, an exception is raised. - If disabled_client_side_validations is set, structural validation is - disabled. This can be useful to troubleshoot data validation problem, such as - when the OpenAPI document validation rules do not match the actual API data - received by the server. - :param server_index: Index to servers configuration. - :param server_variables: Mapping with string values to replace variables in - templated server configuration. The validation of enums is performed for - variables with defined enum values before. - :param server_operation_index: Mapping from operation ID to an index to server - configuration. - :param server_operation_variables: Mapping from operation ID to a mapping with - string values to replace variables in templated server configuration. - The validation of enums is performed for variables with defined enum values before. - :param ssl_ca_cert: str - the path to a file of concatenated CA certificates - in PEM format - - :Example: - - API Key Authentication Example. - Given the following security scheme in the OpenAPI specification: - components: - securitySchemes: - cookieAuth: # name for the security scheme - type: apiKey - in: cookie - name: JSESSIONID # cookie name - - You can programmatically set the cookie: - -conf = openapi_client.Configuration( - api_key={'cookieAuth': 'abc123'} - api_key_prefix={'cookieAuth': 'JSESSIONID'} -) - - The following cookie will be added to the HTTP request: - Cookie: JSESSIONID abc123 + Ref: https://openapi-generator.tech + Do not edit the class manually. + + :param host: Base url + :param api_key: Dict to store API key(s). + Each entry in the dict specifies an API key. + The dict key is the name of the security scheme in the OAS specification. + The dict value is the API key secret. + :param api_key_prefix: Dict to store API prefix (e.g. Bearer) + The dict key is the name of the security scheme in the OAS specification. + The dict value is an API key prefix when generating the auth data. + :param username: Username for HTTP basic authentication + :param password: Password for HTTP basic authentication + :param discard_unknown_keys: Boolean value indicating whether to discard + unknown properties. A server may send a response that includes additional + properties that are not known by the client in the following scenarios: + 1. The OpenAPI document is incomplete, i.e. it does not match the server + implementation. + 2. The client was generated using an older version of the OpenAPI document + and the server has been upgraded since then. + If a schema in the OpenAPI document defines the additionalProperties attribute, + then all undeclared properties received by the server are injected into the + additional properties map. In that case, there are undeclared properties, and + nothing to discard. + :param disabled_client_side_validations (string): Comma-separated list of + JSON schema validation keywords to disable JSON schema structural validation + rules. The following keywords may be specified: multipleOf, maximum, + exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern, + maxItems, minItems. + By default, the validation is performed for data generated locally by the client + and data received from the server, independent of any validation performed by + the server side. If the input data does not satisfy the JSON schema validation + rules specified in the OpenAPI document, an exception is raised. + If disabled_client_side_validations is set, structural validation is + disabled. This can be useful to troubleshoot data validation problem, such as + when the OpenAPI document validation rules do not match the actual API data + received by the server. + :param server_index: Index to servers configuration. + :param server_variables: Mapping with string values to replace variables in + templated server configuration. The validation of enums is performed for + variables with defined enum values before. + :param server_operation_index: Mapping from operation ID to an index to server + configuration. + :param server_operation_variables: Mapping from operation ID to a mapping with + string values to replace variables in templated server configuration. + The validation of enums is performed for variables with defined enum values before. + :param ssl_ca_cert: str - the path to a file of concatenated CA certificates + in PEM format + + :Example: + + API Key Authentication Example. + Given the following security scheme in the OpenAPI specification: + components: + securitySchemes: + cookieAuth: # name for the security scheme + type: apiKey + in: cookie + name: JSESSIONID # cookie name + + You can programmatically set the cookie: + + conf = openapi_client.Configuration( + api_key={'cookieAuth': 'abc123'} + api_key_prefix={'cookieAuth': 'JSESSIONID'} + ) + + The following cookie will be added to the HTTP request: + Cookie: JSESSIONID abc123 """ _default = None - def __init__(self, host=None, - api_key=None, api_key_prefix=None, - access_token=None, - username=None, password=None, - discard_unknown_keys=False, - disabled_client_side_validations="", - server_index=None, server_variables=None, - server_operation_index=None, server_operation_variables=None, - ssl_ca_cert=None, - ): - """Constructor - """ + def __init__( + self, + host=None, + api_key=None, + api_key_prefix=None, + access_token=None, + username=None, + password=None, + discard_unknown_keys=False, + disabled_client_side_validations="", + server_index=None, + server_variables=None, + server_operation_index=None, + server_operation_variables=None, + ssl_ca_cert=None, + ): + """Constructor""" self._base_path = "https://api.groundlight.ai/device-api" if host is None else host """Default Base url """ @@ -155,7 +168,7 @@ def __init__(self, host=None, """ self.logger["package_logger"] = logging.getLogger("openapi_client") self.logger["urllib3_logger"] = logging.getLogger("urllib3") - self.logger_format = '%(asctime)s %(levelname)s %(message)s' + self.logger_format = "%(asctime)s %(levelname)s %(message)s" """Log format """ self.logger_stream_handler = None @@ -206,7 +219,7 @@ def __init__(self, host=None, self.proxy_headers = None """Proxy headers """ - self.safe_chars_for_path_param = '' + self.safe_chars_for_path_param = "" """Safe chars for path_param """ self.retries = None @@ -223,7 +236,7 @@ def __deepcopy__(self, memo): result = cls.__new__(cls) memo[id(self)] = result for k, v in self.__dict__.items(): - if k not in ('logger', 'logger_file_handler'): + if k not in ("logger", "logger_file_handler"): setattr(result, k, copy.deepcopy(v, memo)) # shallow copy of loggers result.logger = copy.copy(self.logger) @@ -234,12 +247,11 @@ def __deepcopy__(self, memo): def __setattr__(self, name, value): object.__setattr__(self, name, value) - if name == 'disabled_client_side_validations': - s = set(filter(None, value.split(','))) + if name == "disabled_client_side_validations": + s = set(filter(None, value.split(","))) for v in s: if v not in JSON_SCHEMA_VALIDATION_KEYWORDS: - raise ApiValueError( - "Invalid keyword: '{0}''".format(v)) + raise ApiValueError("Invalid keyword: '{0}''".format(v)) self._disabled_client_side_validations = s @classmethod @@ -380,9 +392,7 @@ def get_basic_auth_token(self): password = "" if self.password is not None: password = self.password - return urllib3.util.make_headers( - basic_auth=username + ':' + password - ).get('authorization') + return urllib3.util.make_headers(basic_auth=username + ":" + password).get("authorization") def auth_settings(self): """Gets Auth Settings dict for api client. @@ -390,13 +400,13 @@ def auth_settings(self): :return: The Auth Settings information dict. """ auth = {} - if 'ApiToken' in self.api_key: - auth['ApiToken'] = { - 'type': 'api_key', - 'in': 'header', - 'key': 'x-api-token', - 'value': self.get_api_key_with_prefix( - 'ApiToken', + if "ApiToken" in self.api_key: + auth["ApiToken"] = { + "type": "api_key", + "in": "header", + "key": "x-api-token", + "value": self.get_api_key_with_prefix( + "ApiToken", ), } return auth @@ -406,12 +416,13 @@ def to_debug_report(self): :return: The report for debugging. """ - return "Python SDK Debug Report:\n"\ - "OS: {env}\n"\ - "Python Version: {pyversion}\n"\ - "Version of the API: 0.1.0\n"\ - "SDK Package Version: 1.0.0".\ - format(env=sys.platform, pyversion=sys.version) + return ( + "Python SDK Debug Report:\n" + "OS: {env}\n" + "Python Version: {pyversion}\n" + "Version of the API: 0.1.0\n" + "SDK Package Version: 1.0.0".format(env=sys.platform, pyversion=sys.version) + ) def get_host_settings(self): """Gets an array of host settings @@ -420,21 +431,21 @@ def get_host_settings(self): """ return [ { - 'url': "https://api.groundlight.ai/device-api", - 'description': "Prod", + "url": "https://api.groundlight.ai/device-api", + "description": "Prod", }, { - 'url': "https://api.integ.groundlight.ai/device-api", - 'description': "Integ", + "url": "https://api.integ.groundlight.ai/device-api", + "description": "Integ", }, { - 'url': "https://device.positronix.ai/device-api", - 'description': "Device Prod", + "url": "https://device.positronix.ai/device-api", + "description": "Device Prod", }, { - 'url': "https://device.integ.positronix.ai/device-api", - 'description': "Device Integ", - } + "url": "https://device.integ.positronix.ai/device-api", + "description": "Device Integ", + }, ] def get_host_from_settings(self, index, variables=None, servers=None): @@ -455,22 +466,20 @@ def get_host_from_settings(self, index, variables=None, servers=None): except IndexError: raise ValueError( "Invalid index {0} when selecting the host settings. " - "Must be less than {1}".format(index, len(servers))) + "Must be less than {1}".format(index, len(servers)) + ) - url = server['url'] + url = server["url"] # go through variables and replace placeholders - for variable_name, variable in server.get('variables', {}).items(): - used_value = variables.get( - variable_name, variable['default_value']) + for variable_name, variable in server.get("variables", {}).items(): + used_value = variables.get(variable_name, variable["default_value"]) - if 'enum_values' in variable \ - and used_value not in variable['enum_values']: + if "enum_values" in variable and used_value not in variable["enum_values"]: raise ValueError( "The variable `{0}` in the host URL has invalid value " - "{1}. Must be {2}.".format( - variable_name, variables[variable_name], - variable['enum_values'])) + "{1}. Must be {2}.".format(variable_name, variables[variable_name], variable["enum_values"]) + ) url = url.replace("{" + variable_name + "}", used_value) diff --git a/generated/openapi_client/exceptions.py b/generated/openapi_client/exceptions.py index 10f63417..4a1f5fbd 100644 --- a/generated/openapi_client/exceptions.py +++ b/generated/openapi_client/exceptions.py @@ -9,15 +9,13 @@ """ - class OpenApiException(Exception): """The base exception class for all OpenAPIExceptions""" class ApiTypeError(OpenApiException, TypeError): - def __init__(self, msg, path_to_item=None, valid_classes=None, - key_type=None): - """ Raises an exception for TypeErrors + def __init__(self, msg, path_to_item=None, valid_classes=None, key_type=None): + """Raises an exception for TypeErrors Args: msg (str): the exception message @@ -98,7 +96,6 @@ def __init__(self, msg, path_to_item=None): class ApiException(OpenApiException): - def __init__(self, status=None, reason=None, http_resp=None): if http_resp: self.status = http_resp.status @@ -113,11 +110,9 @@ def __init__(self, status=None, reason=None, http_resp=None): def __str__(self): """Custom error messages for exception""" - error_message = "({0})\n"\ - "Reason: {1}\n".format(self.status, self.reason) + error_message = "({0})\n" "Reason: {1}\n".format(self.status, self.reason) if self.headers: - error_message += "HTTP response headers: {0}\n".format( - self.headers) + error_message += "HTTP response headers: {0}\n".format(self.headers) if self.body: error_message += "HTTP response body: {0}\n".format(self.body) @@ -126,25 +121,21 @@ def __str__(self): class NotFoundException(ApiException): - def __init__(self, status=None, reason=None, http_resp=None): super(NotFoundException, self).__init__(status, reason, http_resp) class UnauthorizedException(ApiException): - def __init__(self, status=None, reason=None, http_resp=None): super(UnauthorizedException, self).__init__(status, reason, http_resp) class ForbiddenException(ApiException): - def __init__(self, status=None, reason=None, http_resp=None): super(ForbiddenException, self).__init__(status, reason, http_resp) class ServiceException(ApiException): - def __init__(self, status=None, reason=None, http_resp=None): super(ServiceException, self).__init__(status, reason, http_resp) diff --git a/generated/openapi_client/model/classification_result.py b/generated/openapi_client/model/classification_result.py index a5833d04..003be604 100644 --- a/generated/openapi_client/model/classification_result.py +++ b/generated/openapi_client/model/classification_result.py @@ -25,12 +25,11 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError - class ClassificationResult(ModelNormal): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -55,13 +54,12 @@ class ClassificationResult(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} validations = { - ('confidence',): { - 'inclusive_maximum': 1, - 'inclusive_minimum': 0, + ("confidence",): { + "inclusive_maximum": 1, + "inclusive_minimum": 0, }, } @@ -71,7 +69,17 @@ def additional_properties_type(): This must be a method because a model may have properties that are of type self, this must run after the class is loaded """ - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -86,22 +94,23 @@ def openapi_types(): and the value is attribute type. """ return { - 'label': (str,), # noqa: E501 - 'confidence': (float, none_type,), # noqa: E501 + "label": (str,), # noqa: E501 + "confidence": ( + float, + none_type, + ), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'label': 'label', # noqa: E501 - 'confidence': 'confidence', # noqa: E501 + "label": "label", # noqa: E501 + "confidence": "confidence", # noqa: E501 } - read_only_vars = { - } + read_only_vars = {} _composed_schemas = {} @@ -147,17 +156,18 @@ def _from_openapi_data(cls, label, *args, **kwargs): # noqa: E501 confidence (float, none_type): On a scale of 0 to 1, how confident are we in the predicted label?. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -174,23 +184,27 @@ def _from_openapi_data(cls, label, *args, **kwargs): # noqa: E501 self.label = label for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, label, *args, **kwargs): # noqa: E501 @@ -233,15 +247,16 @@ def __init__(self, label, *args, **kwargs): # noqa: E501 confidence (float, none_type): On a scale of 0 to 1, how confident are we in the predicted label?. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -258,13 +273,17 @@ def __init__(self, label, *args, **kwargs): # noqa: E501 self.label = label for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/detector.py b/generated/openapi_client/model/detector.py index 125889a8..1bef8994 100644 --- a/generated/openapi_client/model/detector.py +++ b/generated/openapi_client/model/detector.py @@ -25,14 +25,15 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError def lazy_import(): from openapi_client.model.detector_type_enum import DetectorTypeEnum - globals()['DetectorTypeEnum'] = DetectorTypeEnum + + globals()["DetectorTypeEnum"] = DetectorTypeEnum class Detector(ModelNormal): @@ -59,16 +60,15 @@ class Detector(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} validations = { - ('name',): { - 'max_length': 200, + ("name",): { + "max_length": 200, }, - ('confidence_threshold',): { - 'inclusive_maximum': 1.0, - 'inclusive_minimum': 0.0, + ("confidence_threshold",): { + "inclusive_maximum": 1.0, + "inclusive_minimum": 0.0, }, } @@ -79,7 +79,17 @@ def additional_properties_type(): of type self, this must run after the class is loaded """ lazy_import() - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -95,36 +105,45 @@ def openapi_types(): """ lazy_import() return { - 'id': (str,), # noqa: E501 - 'type': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 - 'created_at': (datetime,), # noqa: E501 - 'name': (str,), # noqa: E501 - 'query': (str,), # noqa: E501 - 'group_name': (str,), # noqa: E501 - 'confidence_threshold': (float,), # noqa: E501 + "id": (str,), # noqa: E501 + "type": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 + "created_at": (datetime,), # noqa: E501 + "name": (str,), # noqa: E501 + "query": (str,), # noqa: E501 + "group_name": (str,), # noqa: E501 + "confidence_threshold": (float,), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'id': 'id', # noqa: E501 - 'type': 'type', # noqa: E501 - 'created_at': 'created_at', # noqa: E501 - 'name': 'name', # noqa: E501 - 'query': 'query', # noqa: E501 - 'group_name': 'group_name', # noqa: E501 - 'confidence_threshold': 'confidence_threshold', # noqa: E501 + "id": "id", # noqa: E501 + "type": "type", # noqa: E501 + "created_at": "created_at", # noqa: E501 + "name": "name", # noqa: E501 + "query": "query", # noqa: E501 + "group_name": "group_name", # noqa: E501 + "confidence_threshold": "confidence_threshold", # noqa: E501 } read_only_vars = { - 'id', # noqa: E501 - 'type', # noqa: E501 - 'created_at', # noqa: E501 - 'query', # noqa: E501 - 'group_name', # noqa: E501 + "id", # noqa: E501 + "type", # noqa: E501 + "created_at", # noqa: E501 + "query", # noqa: E501 + "group_name", # noqa: E501 } _composed_schemas = {} @@ -176,17 +195,18 @@ def _from_openapi_data(cls, id, type, created_at, name, query, group_name, *args confidence_threshold (float): If the detector's prediction is below this confidence threshold, send the image query for human review.. [optional] if omitted the server will use the default value of 0.9 # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -208,23 +228,27 @@ def _from_openapi_data(cls, id, type, created_at, name, query, group_name, *args self.query = query self.group_name = group_name for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, name, *args, **kwargs): # noqa: E501 @@ -265,15 +289,16 @@ def __init__(self, name, *args, **kwargs): # noqa: E501 confidence_threshold (float): If the detector's prediction is below this confidence threshold, send the image query for human review.. [optional] if omitted the server will use the default value of 0.9 # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -290,13 +315,17 @@ def __init__(self, name, *args, **kwargs): # noqa: E501 self.name = name for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/detector_creation_input.py b/generated/openapi_client/model/detector_creation_input.py index 05517e5c..b8fd476b 100644 --- a/generated/openapi_client/model/detector_creation_input.py +++ b/generated/openapi_client/model/detector_creation_input.py @@ -25,12 +25,11 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError - class DetectorCreationInput(ModelNormal): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -55,25 +54,24 @@ class DetectorCreationInput(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} validations = { - ('name',): { - 'max_length': 200, + ("name",): { + "max_length": 200, }, - ('query',): { - 'max_length': 300, + ("query",): { + "max_length": 300, }, - ('group_name',): { - 'max_length': 100, + ("group_name",): { + "max_length": 100, }, - ('confidence_threshold',): { - 'inclusive_maximum': 1.0, - 'inclusive_minimum': 0.0, + ("confidence_threshold",): { + "inclusive_maximum": 1.0, + "inclusive_minimum": 0.0, }, - ('config_name',): { - 'max_length': 100, + ("config_name",): { + "max_length": 100, }, } @@ -83,7 +81,17 @@ def additional_properties_type(): This must be a method because a model may have properties that are of type self, this must run after the class is loaded """ - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -98,28 +106,29 @@ def openapi_types(): and the value is attribute type. """ return { - 'name': (str,), # noqa: E501 - 'query': (str,), # noqa: E501 - 'group_name': (str,), # noqa: E501 - 'confidence_threshold': (float,), # noqa: E501 - 'config_name': (str, none_type,), # noqa: E501 + "name": (str,), # noqa: E501 + "query": (str,), # noqa: E501 + "group_name": (str,), # noqa: E501 + "confidence_threshold": (float,), # noqa: E501 + "config_name": ( + str, + none_type, + ), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'name': 'name', # noqa: E501 - 'query': 'query', # noqa: E501 - 'group_name': 'group_name', # noqa: E501 - 'confidence_threshold': 'confidence_threshold', # noqa: E501 - 'config_name': 'config_name', # noqa: E501 + "name": "name", # noqa: E501 + "query": "query", # noqa: E501 + "group_name": "group_name", # noqa: E501 + "confidence_threshold": "confidence_threshold", # noqa: E501 + "config_name": "config_name", # noqa: E501 } - read_only_vars = { - } + read_only_vars = {} _composed_schemas = {} @@ -168,17 +177,18 @@ def _from_openapi_data(cls, name, query, *args, **kwargs): # noqa: E501 config_name (str, none_type): (Advanced usage) If your account has multiple named ML configuration options enabled, you can use this field to specify which one you would like to use.. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -196,23 +206,27 @@ def _from_openapi_data(cls, name, query, *args, **kwargs): # noqa: E501 self.name = name self.query = query for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, name, query, *args, **kwargs): # noqa: E501 @@ -258,15 +272,16 @@ def __init__(self, name, query, *args, **kwargs): # noqa: E501 config_name (str, none_type): (Advanced usage) If your account has multiple named ML configuration options enabled, you can use this field to specify which one you would like to use.. [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -284,13 +299,17 @@ def __init__(self, name, query, *args, **kwargs): # noqa: E501 self.name = name self.query = query for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/detector_type_enum.py b/generated/openapi_client/model/detector_type_enum.py index a4a8a871..14779681 100644 --- a/generated/openapi_client/model/detector_type_enum.py +++ b/generated/openapi_client/model/detector_type_enum.py @@ -25,12 +25,11 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError - class DetectorTypeEnum(ModelSimple): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -52,13 +51,12 @@ class DetectorTypeEnum(ModelSimple): """ allowed_values = { - ('value',): { - 'DETECTOR': "detector", + ("value",): { + "DETECTOR": "detector", }, } - validations = { - } + validations = {} additional_properties_type = None @@ -75,28 +73,29 @@ def openapi_types(): and the value is attribute type. """ return { - 'value': (str,), + "value": (str,), } @cached_property def discriminator(): return None - attribute_map = {} read_only_vars = set() _composed_schemas = None - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): @@ -141,24 +140,25 @@ def __init__(self, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "detector" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -175,7 +175,8 @@ def __init__(self, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), @@ -227,26 +228,27 @@ def _from_openapi_data(cls, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) self = super(OpenApiModel, cls).__new__(cls) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "detector" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -263,7 +265,8 @@ def _from_openapi_data(cls, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), diff --git a/generated/openapi_client/model/image_query.py b/generated/openapi_client/model/image_query.py index 418db56d..ece0bf38 100644 --- a/generated/openapi_client/model/image_query.py +++ b/generated/openapi_client/model/image_query.py @@ -25,7 +25,7 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError @@ -34,9 +34,10 @@ def lazy_import(): from openapi_client.model.classification_result import ClassificationResult from openapi_client.model.image_query_type_enum import ImageQueryTypeEnum from openapi_client.model.result_type_enum import ResultTypeEnum - globals()['ClassificationResult'] = ClassificationResult - globals()['ImageQueryTypeEnum'] = ImageQueryTypeEnum - globals()['ResultTypeEnum'] = ResultTypeEnum + + globals()["ClassificationResult"] = ClassificationResult + globals()["ImageQueryTypeEnum"] = ImageQueryTypeEnum + globals()["ResultTypeEnum"] = ResultTypeEnum class ImageQuery(ModelNormal): @@ -63,11 +64,9 @@ class ImageQuery(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} - validations = { - } + validations = {} @cached_property def additional_properties_type(): @@ -76,7 +75,17 @@ def additional_properties_type(): of type self, this must run after the class is loaded """ lazy_import() - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -92,45 +101,76 @@ def openapi_types(): """ lazy_import() return { - 'id': (str,), # noqa: E501 - 'type': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 - 'created_at': (datetime,), # noqa: E501 - 'query': (str,), # noqa: E501 - 'detector_id': (str,), # noqa: E501 - 'result_type': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 - 'result': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501 + "id": (str,), # noqa: E501 + "type": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 + "created_at": (datetime,), # noqa: E501 + "query": (str,), # noqa: E501 + "detector_id": (str,), # noqa: E501 + "result_type": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 + "result": ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'id': 'id', # noqa: E501 - 'type': 'type', # noqa: E501 - 'created_at': 'created_at', # noqa: E501 - 'query': 'query', # noqa: E501 - 'detector_id': 'detector_id', # noqa: E501 - 'result_type': 'result_type', # noqa: E501 - 'result': 'result', # noqa: E501 + "id": "id", # noqa: E501 + "type": "type", # noqa: E501 + "created_at": "created_at", # noqa: E501 + "query": "query", # noqa: E501 + "detector_id": "detector_id", # noqa: E501 + "result_type": "result_type", # noqa: E501 + "result": "result", # noqa: E501 } read_only_vars = { - 'id', # noqa: E501 - 'type', # noqa: E501 - 'created_at', # noqa: E501 - 'query', # noqa: E501 - 'detector_id', # noqa: E501 - 'result_type', # noqa: E501 - 'result', # noqa: E501 + "id", # noqa: E501 + "type", # noqa: E501 + "created_at", # noqa: E501 + "query", # noqa: E501 + "detector_id", # noqa: E501 + "result_type", # noqa: E501 + "result", # noqa: E501 } _composed_schemas = {} @classmethod @convert_js_args_to_python_args - def _from_openapi_data(cls, id, type, created_at, query, detector_id, result_type, result, *args, **kwargs): # noqa: E501 + def _from_openapi_data( + cls, id, type, created_at, query, detector_id, result_type, result, *args, **kwargs + ): # noqa: E501 """ImageQuery - a model defined in OpenAPI Args: @@ -175,17 +215,18 @@ def _from_openapi_data(cls, id, type, created_at, query, detector_id, result_typ _visited_composed_classes = (Animal,) """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -208,23 +249,27 @@ def _from_openapi_data(cls, id, type, created_at, query, detector_id, result_typ self.result_type = result_type self.result = result for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): # noqa: E501 @@ -263,15 +308,16 @@ def __init__(self, *args, **kwargs): # noqa: E501 _visited_composed_classes = (Animal,) """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -287,13 +333,17 @@ def __init__(self, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/image_query_type_enum.py b/generated/openapi_client/model/image_query_type_enum.py index a395dd10..54af9d91 100644 --- a/generated/openapi_client/model/image_query_type_enum.py +++ b/generated/openapi_client/model/image_query_type_enum.py @@ -25,12 +25,11 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError - class ImageQueryTypeEnum(ModelSimple): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -52,13 +51,12 @@ class ImageQueryTypeEnum(ModelSimple): """ allowed_values = { - ('value',): { - 'IMAGE_QUERY': "image_query", + ("value",): { + "IMAGE_QUERY": "image_query", }, } - validations = { - } + validations = {} additional_properties_type = None @@ -75,28 +73,29 @@ def openapi_types(): and the value is attribute type. """ return { - 'value': (str,), + "value": (str,), } @cached_property def discriminator(): return None - attribute_map = {} read_only_vars = set() _composed_schemas = None - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): @@ -141,24 +140,25 @@ def __init__(self, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "image_query" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -175,7 +175,8 @@ def __init__(self, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), @@ -227,26 +228,27 @@ def _from_openapi_data(cls, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) self = super(OpenApiModel, cls).__new__(cls) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "image_query" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -263,7 +265,8 @@ def _from_openapi_data(cls, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), diff --git a/generated/openapi_client/model/paginated_detector_list.py b/generated/openapi_client/model/paginated_detector_list.py index 1973ecc2..759292cc 100644 --- a/generated/openapi_client/model/paginated_detector_list.py +++ b/generated/openapi_client/model/paginated_detector_list.py @@ -25,14 +25,15 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError def lazy_import(): from openapi_client.model.detector import Detector - globals()['Detector'] = Detector + + globals()["Detector"] = Detector class PaginatedDetectorList(ModelNormal): @@ -59,11 +60,9 @@ class PaginatedDetectorList(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} - validations = { - } + validations = {} @cached_property def additional_properties_type(): @@ -72,7 +71,17 @@ def additional_properties_type(): of type self, this must run after the class is loaded """ lazy_import() - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -88,26 +97,30 @@ def openapi_types(): """ lazy_import() return { - 'count': (int,), # noqa: E501 - 'next': (str, none_type,), # noqa: E501 - 'previous': (str, none_type,), # noqa: E501 - 'results': ([Detector],), # noqa: E501 + "count": (int,), # noqa: E501 + "next": ( + str, + none_type, + ), # noqa: E501 + "previous": ( + str, + none_type, + ), # noqa: E501 + "results": ([Detector],), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'count': 'count', # noqa: E501 - 'next': 'next', # noqa: E501 - 'previous': 'previous', # noqa: E501 - 'results': 'results', # noqa: E501 + "count": "count", # noqa: E501 + "next": "next", # noqa: E501 + "previous": "previous", # noqa: E501 + "results": "results", # noqa: E501 } - read_only_vars = { - } + read_only_vars = {} _composed_schemas = {} @@ -153,17 +166,18 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 results ([Detector]): [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -179,23 +193,27 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): # noqa: E501 @@ -238,15 +256,16 @@ def __init__(self, *args, **kwargs): # noqa: E501 results ([Detector]): [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -262,13 +281,17 @@ def __init__(self, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/paginated_image_query_list.py b/generated/openapi_client/model/paginated_image_query_list.py index a7d7abde..ae233ad1 100644 --- a/generated/openapi_client/model/paginated_image_query_list.py +++ b/generated/openapi_client/model/paginated_image_query_list.py @@ -25,14 +25,15 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError def lazy_import(): from openapi_client.model.image_query import ImageQuery - globals()['ImageQuery'] = ImageQuery + + globals()["ImageQuery"] = ImageQuery class PaginatedImageQueryList(ModelNormal): @@ -59,11 +60,9 @@ class PaginatedImageQueryList(ModelNormal): as additional properties values. """ - allowed_values = { - } + allowed_values = {} - validations = { - } + validations = {} @cached_property def additional_properties_type(): @@ -72,7 +71,17 @@ def additional_properties_type(): of type self, this must run after the class is loaded """ lazy_import() - return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + return ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ) # noqa: E501 _nullable = False @@ -88,26 +97,30 @@ def openapi_types(): """ lazy_import() return { - 'count': (int,), # noqa: E501 - 'next': (str, none_type,), # noqa: E501 - 'previous': (str, none_type,), # noqa: E501 - 'results': ([ImageQuery],), # noqa: E501 + "count": (int,), # noqa: E501 + "next": ( + str, + none_type, + ), # noqa: E501 + "previous": ( + str, + none_type, + ), # noqa: E501 + "results": ([ImageQuery],), # noqa: E501 } @cached_property def discriminator(): return None - attribute_map = { - 'count': 'count', # noqa: E501 - 'next': 'next', # noqa: E501 - 'previous': 'previous', # noqa: E501 - 'results': 'results', # noqa: E501 + "count": "count", # noqa: E501 + "next": "next", # noqa: E501 + "previous": "previous", # noqa: E501 + "results": "results", # noqa: E501 } - read_only_vars = { - } + read_only_vars = {} _composed_schemas = {} @@ -153,17 +166,18 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 results ([ImageQuery]): [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) self = super(OpenApiModel, cls).__new__(cls) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -179,23 +193,27 @@ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) return self - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): # noqa: E501 @@ -238,15 +256,16 @@ def __init__(self, *args, **kwargs): # noqa: E501 results ([ImageQuery]): [optional] # noqa: E501 """ - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _path_to_item = kwargs.pop('_path_to_item', ()) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _path_to_item = kwargs.pop("_path_to_item", ()) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -262,13 +281,17 @@ def __init__(self, *args, **kwargs): # noqa: E501 self._visited_composed_classes = _visited_composed_classes + (self.__class__,) for var_name, var_value in kwargs.items(): - if var_name not in self.attribute_map and \ - self._configuration is not None and \ - self._configuration.discard_unknown_keys and \ - self.additional_properties_type is None: + if ( + var_name not in self.attribute_map + and self._configuration is not None + and self._configuration.discard_unknown_keys + and self.additional_properties_type is None + ): # discard variable. continue setattr(self, var_name, var_value) if var_name in self.read_only_vars: - raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " - f"class with read only attributes.") + raise ApiAttributeError( + f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes." + ) diff --git a/generated/openapi_client/model/result_type_enum.py b/generated/openapi_client/model/result_type_enum.py index 9c5d01d3..e5de74ee 100644 --- a/generated/openapi_client/model/result_type_enum.py +++ b/generated/openapi_client/model/result_type_enum.py @@ -25,12 +25,11 @@ file_type, none_type, validate_get_composed_info, - OpenApiModel + OpenApiModel, ) from openapi_client.exceptions import ApiAttributeError - class ResultTypeEnum(ModelSimple): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -52,13 +51,12 @@ class ResultTypeEnum(ModelSimple): """ allowed_values = { - ('value',): { - 'BINARY_CLASSIFICATION': "binary_classification", + ("value",): { + "BINARY_CLASSIFICATION": "binary_classification", }, } - validations = { - } + validations = {} additional_properties_type = None @@ -75,28 +73,29 @@ def openapi_types(): and the value is attribute type. """ return { - 'value': (str,), + "value": (str,), } @cached_property def discriminator(): return None - attribute_map = {} read_only_vars = set() _composed_schemas = None - required_properties = set([ - '_data_store', - '_check_type', - '_spec_property_naming', - '_path_to_item', - '_configuration', - '_visited_composed_classes', - ]) + required_properties = set( + [ + "_data_store", + "_check_type", + "_spec_property_naming", + "_path_to_item", + "_configuration", + "_visited_composed_classes", + ] + ) @convert_js_args_to_python_args def __init__(self, *args, **kwargs): @@ -141,24 +140,25 @@ def __init__(self, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "binary_classification" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -175,7 +175,8 @@ def __init__(self, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), @@ -227,26 +228,27 @@ def _from_openapi_data(cls, *args, **kwargs): _visited_composed_classes = (Animal,) """ # required up here when default value is not given - _path_to_item = kwargs.pop('_path_to_item', ()) + _path_to_item = kwargs.pop("_path_to_item", ()) self = super(OpenApiModel, cls).__new__(cls) - if 'value' in kwargs: - value = kwargs.pop('value') + if "value" in kwargs: + value = kwargs.pop("value") elif args: args = list(args) value = args.pop(0) else: value = "binary_classification" - _check_type = kwargs.pop('_check_type', True) - _spec_property_naming = kwargs.pop('_spec_property_naming', False) - _configuration = kwargs.pop('_configuration', None) - _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + _check_type = kwargs.pop("_check_type", True) + _spec_property_naming = kwargs.pop("_spec_property_naming", False) + _configuration = kwargs.pop("_configuration", None) + _visited_composed_classes = kwargs.pop("_visited_composed_classes", ()) if args: raise ApiTypeError( - "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." + % ( args, self.__class__.__name__, ), @@ -263,7 +265,8 @@ def _from_openapi_data(cls, *args, **kwargs): self.value = value if kwargs: raise ApiTypeError( - "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." + % ( kwargs, self.__class__.__name__, ), diff --git a/generated/openapi_client/model_utils.py b/generated/openapi_client/model_utils.py index 1132bb8d..de6c1216 100644 --- a/generated/openapi_client/model_utils.py +++ b/generated/openapi_client/model_utils.py @@ -33,6 +33,7 @@ def convert_js_args_to_python_args(fn): from functools import wraps + @wraps(fn) def wrapped_init(_self, *args, **kwargs): """ @@ -40,10 +41,11 @@ def wrapped_init(_self, *args, **kwargs): parameter of a class method. During generation, `self` attributes are mapped to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts. """ - spec_property_naming = kwargs.get('_spec_property_naming', False) + spec_property_naming = kwargs.get("_spec_property_naming", False) if spec_property_naming: kwargs = change_keys_js_to_python(kwargs, _self if isinstance(_self, type) else _self.__class__) return fn(_self, *args, **kwargs) + return wrapped_init @@ -51,7 +53,7 @@ class cached_property(object): # this caches the result of the function call for fn with no inputs # use this as a decorator on function methods that you want converted # into cached properties - result_key = '_results' + result_key = "_results" def __init__(self, fn): self._fn = fn @@ -67,6 +69,7 @@ def __get__(self, instance, cls=None): PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type) + def allows_single_value_input(cls): """ This function returns True if the input composed schema model or any @@ -80,17 +83,15 @@ def allows_single_value_input(cls): - null TODO: lru_cache this """ - if ( - issubclass(cls, ModelSimple) or - cls in PRIMITIVE_TYPES - ): + if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES: return True elif issubclass(cls, ModelComposed): - if not cls._composed_schemas['oneOf']: + if not cls._composed_schemas["oneOf"]: return False - return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf']) + return any(allows_single_value_input(c) for c in cls._composed_schemas["oneOf"]) return False + def composed_model_input_classes(cls): """ This function returns a list of the possible models that can be accepted as @@ -105,11 +106,11 @@ def composed_model_input_classes(cls): else: return get_discriminated_classes(cls) elif issubclass(cls, ModelComposed): - if not cls._composed_schemas['oneOf']: + if not cls._composed_schemas["oneOf"]: return [] if cls.discriminator is None: input_classes = [] - for c in cls._composed_schemas['oneOf']: + for c in cls._composed_schemas["oneOf"]: input_classes.extend(composed_model_input_classes(c)) return input_classes else: @@ -131,46 +132,28 @@ def set_attribute(self, name, value): if name in self.openapi_types: required_types_mixed = self.openapi_types[name] elif self.additional_properties_type is None: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + raise ApiAttributeError("{0} has no attribute '{1}'".format(type(self).__name__, name), path_to_item) elif self.additional_properties_type is not None: required_types_mixed = self.additional_properties_type if get_simple_class(name) != str: - error_msg = type_error_message( - var_name=name, - var_value=name, - valid_classes=(str,), - key_type=True - ) - raise ApiTypeError( - error_msg, - path_to_item=path_to_item, - valid_classes=(str,), - key_type=True - ) + error_msg = type_error_message(var_name=name, var_value=name, valid_classes=(str,), key_type=True) + raise ApiTypeError(error_msg, path_to_item=path_to_item, valid_classes=(str,), key_type=True) if self._check_type: value = validate_and_convert_types( - value, required_types_mixed, path_to_item, self._spec_property_naming, - self._check_type, configuration=self._configuration) - if (name,) in self.allowed_values: - check_allowed_values( - self.allowed_values, - (name,), - value - ) - if (name,) in self.validations: - check_validations( - self.validations, - (name,), value, - self._configuration + required_types_mixed, + path_to_item, + self._spec_property_naming, + self._check_type, + configuration=self._configuration, ) - self.__dict__['_data_store'][name] = value + if (name,) in self.allowed_values: + check_allowed_values(self.allowed_values, (name,), value) + if (name,) in self.validations: + check_validations(self.validations, (name,), value, self._configuration) + self.__dict__["_data_store"][name] = value def __repr__(self): """For `print` and `pprint`""" @@ -207,7 +190,6 @@ def __deepcopy__(self, memo): setattr(new_inst, k, deepcopy(v, memo)) return new_inst - def __new__(cls, *args, **kwargs): # this function uses the discriminator to # pick a new schema/class to instantiate because a discriminator @@ -224,12 +206,8 @@ def __new__(cls, *args, **kwargs): oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) return oneof_instance - - visited_composed_classes = kwargs.get('_visited_composed_classes', ()) - if ( - cls.discriminator is None or - cls in visited_composed_classes - ): + visited_composed_classes = kwargs.get("_visited_composed_classes", ()) + if cls.discriminator is None or cls in visited_composed_classes: # Use case 1: this openapi schema (cls) does not have a discriminator # Use case 2: we have already visited this class before and are sure that we # want to instantiate it this time. We have visited this class deserializing @@ -259,28 +237,24 @@ def __new__(cls, *args, **kwargs): discr_value = kwargs[discr_propertyname_py] else: # The input data does not contain the discriminator property. - path_to_item = kwargs.get('_path_to_item', ()) + path_to_item = kwargs.get("_path_to_item", ()) raise ApiValueError( "Cannot deserialize input data due to missing discriminator. " - "The discriminator property '%s' is missing at path: %s" % - (discr_propertyname_js, path_to_item) + "The discriminator property '%s' is missing at path: %s" % (discr_propertyname_js, path_to_item) ) # Implementation note: the last argument to get_discriminator_class # is a list of visited classes. get_discriminator_class may recursively # call itself and update the list of visited classes, and the initial # value must be an empty list. Hence not using 'visited_composed_classes' - new_cls = get_discriminator_class( - cls, discr_propertyname_py, discr_value, []) + new_cls = get_discriminator_class(cls, discr_propertyname_py, discr_value, []) if new_cls is None: - path_to_item = kwargs.get('_path_to_item', ()) - disc_prop_value = kwargs.get( - discr_propertyname_js, kwargs.get(discr_propertyname_py)) + path_to_item = kwargs.get("_path_to_item", ()) + disc_prop_value = kwargs.get(discr_propertyname_js, kwargs.get(discr_propertyname_py)) raise ApiValueError( "Cannot deserialize input data due to invalid discriminator " "value. The OpenAPI document has no mapping for discriminator " - "property '%s'='%s' at path: %s" % - (discr_propertyname_js, disc_prop_value, path_to_item) + "property '%s'='%s' at path: %s" % (discr_propertyname_js, disc_prop_value, path_to_item) ) if new_cls in visited_composed_classes: @@ -305,13 +279,11 @@ def __new__(cls, *args, **kwargs): # Build a list containing all oneOf and anyOf descendants. oneof_anyof_classes = None if cls._composed_schemas is not None: - oneof_anyof_classes = ( - cls._composed_schemas.get('oneOf', ()) + - cls._composed_schemas.get('anyOf', ())) + oneof_anyof_classes = cls._composed_schemas.get("oneOf", ()) + cls._composed_schemas.get("anyOf", ()) oneof_anyof_child = new_cls in oneof_anyof_classes - kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + kwargs["_visited_composed_classes"] = visited_composed_classes + (cls,) - if cls._composed_schemas.get('allOf') and oneof_anyof_child: + if cls._composed_schemas.get("allOf") and oneof_anyof_child: # Validate that we can make self because when we make the # new_cls it will not include the allOf validations in self self_inst = super(OpenApiModel, cls).__new__(cls) @@ -326,7 +298,6 @@ def __new__(cls, *args, **kwargs): return new_inst - @classmethod @convert_js_args_to_python_args def _new_from_openapi_data(cls, *args, **kwargs): @@ -345,12 +316,8 @@ def _new_from_openapi_data(cls, *args, **kwargs): oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) return oneof_instance - - visited_composed_classes = kwargs.get('_visited_composed_classes', ()) - if ( - cls.discriminator is None or - cls in visited_composed_classes - ): + visited_composed_classes = kwargs.get("_visited_composed_classes", ()) + if cls.discriminator is None or cls in visited_composed_classes: # Use case 1: this openapi schema (cls) does not have a discriminator # Use case 2: we have already visited this class before and are sure that we # want to instantiate it this time. We have visited this class deserializing @@ -380,28 +347,24 @@ def _new_from_openapi_data(cls, *args, **kwargs): discr_value = kwargs[discr_propertyname_py] else: # The input data does not contain the discriminator property. - path_to_item = kwargs.get('_path_to_item', ()) + path_to_item = kwargs.get("_path_to_item", ()) raise ApiValueError( "Cannot deserialize input data due to missing discriminator. " - "The discriminator property '%s' is missing at path: %s" % - (discr_propertyname_js, path_to_item) + "The discriminator property '%s' is missing at path: %s" % (discr_propertyname_js, path_to_item) ) # Implementation note: the last argument to get_discriminator_class # is a list of visited classes. get_discriminator_class may recursively # call itself and update the list of visited classes, and the initial # value must be an empty list. Hence not using 'visited_composed_classes' - new_cls = get_discriminator_class( - cls, discr_propertyname_py, discr_value, []) + new_cls = get_discriminator_class(cls, discr_propertyname_py, discr_value, []) if new_cls is None: - path_to_item = kwargs.get('_path_to_item', ()) - disc_prop_value = kwargs.get( - discr_propertyname_js, kwargs.get(discr_propertyname_py)) + path_to_item = kwargs.get("_path_to_item", ()) + disc_prop_value = kwargs.get(discr_propertyname_js, kwargs.get(discr_propertyname_py)) raise ApiValueError( "Cannot deserialize input data due to invalid discriminator " "value. The OpenAPI document has no mapping for discriminator " - "property '%s'='%s' at path: %s" % - (discr_propertyname_js, disc_prop_value, path_to_item) + "property '%s'='%s' at path: %s" % (discr_propertyname_js, disc_prop_value, path_to_item) ) if new_cls in visited_composed_classes: @@ -426,18 +389,15 @@ def _new_from_openapi_data(cls, *args, **kwargs): # Build a list containing all oneOf and anyOf descendants. oneof_anyof_classes = None if cls._composed_schemas is not None: - oneof_anyof_classes = ( - cls._composed_schemas.get('oneOf', ()) + - cls._composed_schemas.get('anyOf', ())) + oneof_anyof_classes = cls._composed_schemas.get("oneOf", ()) + cls._composed_schemas.get("anyOf", ()) oneof_anyof_child = new_cls in oneof_anyof_classes - kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + kwargs["_visited_composed_classes"] = visited_composed_classes + (cls,) - if cls._composed_schemas.get('allOf') and oneof_anyof_child: + if cls._composed_schemas.get("allOf") and oneof_anyof_child: # Validate that we can make self because when we make the # new_cls it will not include the allOf validations in self self_inst = cls._from_openapi_data(*args, **kwargs) - new_inst = new_cls._new_from_openapi_data(*args, **kwargs) return new_inst @@ -459,7 +419,7 @@ def get(self, name, default=None): if name in self.required_properties: return self.__dict__[name] - return self.__dict__['_data_store'].get(name, default) + return self.__dict__["_data_store"].get(name, default) def __getitem__(self, name): """get the value of an attribute using square-bracket notation: `instance[attr]`""" @@ -467,9 +427,7 @@ def __getitem__(self, name): return self.get(name) raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - [e for e in [self._path_to_item, name] if e] + "{0} has no attribute '{1}'".format(type(self).__name__, name), [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): @@ -477,7 +435,7 @@ def __contains__(self, name): if name in self.required_properties: return name in self.__dict__ - return name in self.__dict__['_data_store'] + return name in self.__dict__["_data_store"] def to_str(self): """Returns the string representation of the model""" @@ -488,8 +446,8 @@ def __eq__(self, other): if not isinstance(other, self.__class__): return False - this_val = self._data_store['value'] - that_val = other._data_store['value'] + this_val = self._data_store["value"] + that_val = other._data_store["value"] types = set() types.add(this_val.__class__) types.add(that_val.__class__) @@ -514,7 +472,7 @@ def get(self, name, default=None): if name in self.required_properties: return self.__dict__[name] - return self.__dict__['_data_store'].get(name, default) + return self.__dict__["_data_store"].get(name, default) def __getitem__(self, name): """get the value of an attribute using square-bracket notation: `instance[attr]`""" @@ -522,9 +480,7 @@ def __getitem__(self, name): return self.get(name) raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - [e for e in [self._path_to_item, name] if e] + "{0} has no attribute '{1}'".format(type(self).__name__, name), [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): @@ -532,7 +488,7 @@ def __contains__(self, name): if name in self.required_properties: return name in self.__dict__ - return name in self.__dict__['_data_store'] + return name in self.__dict__["_data_store"] def to_dict(self): """Returns the model properties as a dict""" @@ -617,9 +573,8 @@ def __setitem__(self, name, value): """ if name not in self.openapi_types: raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - [e for e in [self._path_to_item, name] if e] + "{0} has no attribute '{1}'".format(type(self).__name__, name), + [e for e in [self._path_to_item, name] if e], ) # attribute must be set on self and composed instances self.set_attribute(name, value) @@ -627,7 +582,7 @@ def __setitem__(self, name, value): setattr(model_instance, name, value) if name not in self._var_name_to_model_instances: # we assigned an additional property - self.__dict__['_var_name_to_model_instances'][name] = self._composed_instances + [self] + self.__dict__["_var_name_to_model_instances"][name] = self._composed_instances + [self] return None __unset_attribute_value__ = object() @@ -660,7 +615,7 @@ def get(self, name, default=None): "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - [e for e in [self._path_to_item, name] if e] + [e for e in [self._path_to_item, name] if e], ) def __getitem__(self, name): @@ -668,9 +623,8 @@ def __getitem__(self, name): value = self.get(name, self.__unset_attribute_value__) if value is self.__unset_attribute_value__: raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - [e for e in [self._path_to_item, name] if e] + "{0} has no attribute '{1}'".format(type(self).__name__, name), + [e for e in [self._path_to_item, name] if e], ) return value @@ -680,8 +634,7 @@ def __contains__(self, name): if name in self.required_properties: return name in self.__dict__ - model_instances = self._var_name_to_model_instances.get( - name, self._additional_properties_model_instances) + model_instances = self._var_name_to_model_instances.get(name, self._additional_properties_model_instances) if model_instances: for model_instance in model_instances: @@ -720,7 +673,7 @@ def __eq__(self, other): ModelComposed: 0, ModelNormal: 1, ModelSimple: 2, - none_type: 3, # The type of 'None'. + none_type: 3, # The type of 'None'. list: 4, dict: 5, float: 6, @@ -729,7 +682,7 @@ def __eq__(self, other): datetime: 9, date: 10, str: 11, - file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type. + file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type. } # these are used to limit what type conversions we try to do @@ -738,7 +691,7 @@ def __eq__(self, other): UPCONVERSION_TYPE_PAIRS = ( (str, datetime), (str, date), - (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. + (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (list, ModelComposed), (dict, ModelComposed), (str, ModelComposed), @@ -785,7 +738,7 @@ def __eq__(self, other): (str, date), # (int, str), # (float, str), - (str, file_type) + (str, file_type), ), } @@ -842,41 +795,22 @@ def check_allowed_values(allowed_values, input_variable_path, input_values): are checking to see if they are in allowed_values """ these_allowed_values = list(allowed_values[input_variable_path].values()) - if (isinstance(input_values, list) - and not set(input_values).issubset( - set(these_allowed_values))): - invalid_values = ", ".join( - map(str, set(input_values) - set(these_allowed_values))), + if isinstance(input_values, list) and not set(input_values).issubset(set(these_allowed_values)): + invalid_values = (", ".join(map(str, set(input_values) - set(these_allowed_values))),) raise ApiValueError( - "Invalid values for `%s` [%s], must be a subset of [%s]" % - ( - input_variable_path[0], - invalid_values, - ", ".join(map(str, these_allowed_values)) - ) + "Invalid values for `%s` [%s], must be a subset of [%s]" + % (input_variable_path[0], invalid_values, ", ".join(map(str, these_allowed_values))) ) - elif (isinstance(input_values, dict) - and not set( - input_values.keys()).issubset(set(these_allowed_values))): - invalid_values = ", ".join( - map(str, set(input_values.keys()) - set(these_allowed_values))) + elif isinstance(input_values, dict) and not set(input_values.keys()).issubset(set(these_allowed_values)): + invalid_values = ", ".join(map(str, set(input_values.keys()) - set(these_allowed_values))) raise ApiValueError( - "Invalid keys in `%s` [%s], must be a subset of [%s]" % - ( - input_variable_path[0], - invalid_values, - ", ".join(map(str, these_allowed_values)) - ) + "Invalid keys in `%s` [%s], must be a subset of [%s]" + % (input_variable_path[0], invalid_values, ", ".join(map(str, these_allowed_values))) ) - elif (not isinstance(input_values, (list, dict)) - and input_values not in these_allowed_values): + elif not isinstance(input_values, (list, dict)) and input_values not in these_allowed_values: raise ApiValueError( - "Invalid value for `%s` (%s), must be one of %s" % - ( - input_variable_path[0], - input_values, - these_allowed_values - ) + "Invalid value for `%s` (%s), must be one of %s" + % (input_variable_path[0], input_values, these_allowed_values) ) @@ -890,14 +824,14 @@ def is_json_validation_enabled(schema_keyword, configuration=None): configuration (Configuration): the configuration class. """ - return (configuration is None or - not hasattr(configuration, '_disabled_client_side_validations') or - schema_keyword not in configuration._disabled_client_side_validations) + return ( + configuration is None + or not hasattr(configuration, "_disabled_client_side_validations") + or schema_keyword not in configuration._disabled_client_side_validations + ) -def check_validations( - validations, input_variable_path, input_values, - configuration=None): +def check_validations(validations, input_variable_path, input_values, configuration=None): """Raises an exception if the input_values are invalid Args: @@ -912,66 +846,60 @@ def check_validations( return current_validations = validations[input_variable_path] - if (is_json_validation_enabled('multipleOf', configuration) and - 'multiple_of' in current_validations and - isinstance(input_values, (int, float)) and - not (float(input_values) / current_validations['multiple_of']).is_integer()): + if ( + is_json_validation_enabled("multipleOf", configuration) + and "multiple_of" in current_validations + and isinstance(input_values, (int, float)) + and not (float(input_values) / current_validations["multiple_of"]).is_integer() + ): # Note 'multipleOf' will be as good as the floating point arithmetic. raise ApiValueError( "Invalid value for `%s`, value must be a multiple of " - "`%s`" % ( - input_variable_path[0], - current_validations['multiple_of'] - ) + "`%s`" % (input_variable_path[0], current_validations["multiple_of"]) ) - if (is_json_validation_enabled('maxLength', configuration) and - 'max_length' in current_validations and - len(input_values) > current_validations['max_length']): + if ( + is_json_validation_enabled("maxLength", configuration) + and "max_length" in current_validations + and len(input_values) > current_validations["max_length"] + ): raise ApiValueError( "Invalid value for `%s`, length must be less than or equal to " - "`%s`" % ( - input_variable_path[0], - current_validations['max_length'] - ) + "`%s`" % (input_variable_path[0], current_validations["max_length"]) ) - if (is_json_validation_enabled('minLength', configuration) and - 'min_length' in current_validations and - len(input_values) < current_validations['min_length']): + if ( + is_json_validation_enabled("minLength", configuration) + and "min_length" in current_validations + and len(input_values) < current_validations["min_length"] + ): raise ApiValueError( "Invalid value for `%s`, length must be greater than or equal to " - "`%s`" % ( - input_variable_path[0], - current_validations['min_length'] - ) + "`%s`" % (input_variable_path[0], current_validations["min_length"]) ) - if (is_json_validation_enabled('maxItems', configuration) and - 'max_items' in current_validations and - len(input_values) > current_validations['max_items']): + if ( + is_json_validation_enabled("maxItems", configuration) + and "max_items" in current_validations + and len(input_values) > current_validations["max_items"] + ): raise ApiValueError( "Invalid value for `%s`, number of items must be less than or " - "equal to `%s`" % ( - input_variable_path[0], - current_validations['max_items'] - ) + "equal to `%s`" % (input_variable_path[0], current_validations["max_items"]) ) - if (is_json_validation_enabled('minItems', configuration) and - 'min_items' in current_validations and - len(input_values) < current_validations['min_items']): + if ( + is_json_validation_enabled("minItems", configuration) + and "min_items" in current_validations + and len(input_values) < current_validations["min_items"] + ): raise ValueError( "Invalid value for `%s`, number of items must be greater than or " - "equal to `%s`" % ( - input_variable_path[0], - current_validations['min_items'] - ) + "equal to `%s`" % (input_variable_path[0], current_validations["min_items"]) ) - items = ('exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum', - 'inclusive_minimum') - if (any(item in current_validations for item in items)): + items = ("exclusive_maximum", "inclusive_maximum", "exclusive_minimum", "inclusive_minimum") + if any(item in current_validations for item in items): if isinstance(input_values, list): max_val = max(input_values) min_val = min(input_values) @@ -982,57 +910,55 @@ def check_validations( max_val = input_values min_val = input_values - if (is_json_validation_enabled('exclusiveMaximum', configuration) and - 'exclusive_maximum' in current_validations and - max_val >= current_validations['exclusive_maximum']): + if ( + is_json_validation_enabled("exclusiveMaximum", configuration) + and "exclusive_maximum" in current_validations + and max_val >= current_validations["exclusive_maximum"] + ): raise ApiValueError( - "Invalid value for `%s`, must be a value less than `%s`" % ( - input_variable_path[0], - current_validations['exclusive_maximum'] - ) + "Invalid value for `%s`, must be a value less than `%s`" + % (input_variable_path[0], current_validations["exclusive_maximum"]) ) - if (is_json_validation_enabled('maximum', configuration) and - 'inclusive_maximum' in current_validations and - max_val > current_validations['inclusive_maximum']): + if ( + is_json_validation_enabled("maximum", configuration) + and "inclusive_maximum" in current_validations + and max_val > current_validations["inclusive_maximum"] + ): raise ApiValueError( "Invalid value for `%s`, must be a value less than or equal to " - "`%s`" % ( - input_variable_path[0], - current_validations['inclusive_maximum'] - ) + "`%s`" % (input_variable_path[0], current_validations["inclusive_maximum"]) ) - if (is_json_validation_enabled('exclusiveMinimum', configuration) and - 'exclusive_minimum' in current_validations and - min_val <= current_validations['exclusive_minimum']): + if ( + is_json_validation_enabled("exclusiveMinimum", configuration) + and "exclusive_minimum" in current_validations + and min_val <= current_validations["exclusive_minimum"] + ): raise ApiValueError( - "Invalid value for `%s`, must be a value greater than `%s`" % - ( - input_variable_path[0], - current_validations['exclusive_maximum'] - ) + "Invalid value for `%s`, must be a value greater than `%s`" + % (input_variable_path[0], current_validations["exclusive_maximum"]) ) - if (is_json_validation_enabled('minimum', configuration) and - 'inclusive_minimum' in current_validations and - min_val < current_validations['inclusive_minimum']): + if ( + is_json_validation_enabled("minimum", configuration) + and "inclusive_minimum" in current_validations + and min_val < current_validations["inclusive_minimum"] + ): raise ApiValueError( "Invalid value for `%s`, must be a value greater than or equal " - "to `%s`" % ( - input_variable_path[0], - current_validations['inclusive_minimum'] - ) + "to `%s`" % (input_variable_path[0], current_validations["inclusive_minimum"]) ) - flags = current_validations.get('regex', {}).get('flags', 0) - if (is_json_validation_enabled('pattern', configuration) and - 'regex' in current_validations and - not re.search(current_validations['regex']['pattern'], - input_values, flags=flags)): + flags = current_validations.get("regex", {}).get("flags", 0) + if ( + is_json_validation_enabled("pattern", configuration) + and "regex" in current_validations + and not re.search(current_validations["regex"]["pattern"], input_values, flags=flags) + ): err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % ( - input_variable_path[0], - current_validations['regex']['pattern'] - ) + input_variable_path[0], + current_validations["regex"]["pattern"], + ) if flags != 0: # Don't print the regex flags if the flags are not # specified in the OAS document. @@ -1057,28 +983,21 @@ def index_getter(class_or_instance): return COERCION_INDEX_BY_TYPE[list] elif isinstance(class_or_instance, dict): return COERCION_INDEX_BY_TYPE[dict] - elif (inspect.isclass(class_or_instance) - and issubclass(class_or_instance, ModelComposed)): + elif inspect.isclass(class_or_instance) and issubclass(class_or_instance, ModelComposed): return COERCION_INDEX_BY_TYPE[ModelComposed] - elif (inspect.isclass(class_or_instance) - and issubclass(class_or_instance, ModelNormal)): + elif inspect.isclass(class_or_instance) and issubclass(class_or_instance, ModelNormal): return COERCION_INDEX_BY_TYPE[ModelNormal] - elif (inspect.isclass(class_or_instance) - and issubclass(class_or_instance, ModelSimple)): + elif inspect.isclass(class_or_instance) and issubclass(class_or_instance, ModelSimple): return COERCION_INDEX_BY_TYPE[ModelSimple] elif class_or_instance in COERCION_INDEX_BY_TYPE: return COERCION_INDEX_BY_TYPE[class_or_instance] raise ApiValueError("Unsupported type: %s" % class_or_instance) - sorted_types = sorted( - required_types, - key=lambda class_or_instance: index_getter(class_or_instance) - ) + sorted_types = sorted(required_types, key=lambda class_or_instance: index_getter(class_or_instance)) return sorted_types -def remove_uncoercible(required_types_classes, current_item, spec_property_naming, - must_convert=True): +def remove_uncoercible(required_types_classes, current_item, spec_property_naming, must_convert=True): """Only keeps the type conversions that are possible Args: @@ -1123,6 +1042,7 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin results_classes.append(required_type_class) return results_classes + def get_discriminated_classes(cls): """ Returns all the classes that a discriminator converts to @@ -1133,7 +1053,7 @@ def get_discriminated_classes(cls): if is_type_nullable(cls): possible_classes.append(cls) for discr_cls in cls.discriminator[key].values(): - if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None: + if hasattr(discr_cls, "discriminator") and discr_cls.discriminator is not None: possible_classes.extend(get_discriminated_classes(discr_cls)) else: possible_classes.append(discr_cls) @@ -1145,7 +1065,7 @@ def get_possible_classes(cls, from_server_context): possible_classes = [cls] if from_server_context: return possible_classes - if hasattr(cls, 'discriminator') and cls.discriminator is not None: + if hasattr(cls, "discriminator") and cls.discriminator is not None: possible_classes = [] possible_classes.extend(get_discriminated_classes(cls)) elif issubclass(cls, ModelComposed): @@ -1201,11 +1121,10 @@ def change_keys_js_to_python(input_dict, model_class): document). """ - if getattr(model_class, 'attribute_map', None) is None: + if getattr(model_class, "attribute_map", None) is None: return input_dict output_dict = {} - reversed_attr_map = {value: key for key, value in - model_class.attribute_map.items()} + reversed_attr_map = {value: key for key, value in model_class.attribute_map.items()} for javascript_key, value in input_dict.items(): python_key = reversed_attr_map.get(javascript_key) if python_key is None: @@ -1218,17 +1137,9 @@ def change_keys_js_to_python(input_dict, model_class): def get_type_error(var_value, path_to_item, valid_classes, key_type=False): error_msg = type_error_message( - var_name=path_to_item[-1], - var_value=var_value, - valid_classes=valid_classes, - key_type=key_type - ) - return ApiTypeError( - error_msg, - path_to_item=path_to_item, - valid_classes=valid_classes, - key_type=key_type + var_name=path_to_item[-1], var_value=var_value, valid_classes=valid_classes, key_type=key_type ) + return ApiTypeError(error_msg, path_to_item=path_to_item, valid_classes=valid_classes, key_type=key_type) def deserialize_primitive(data, klass, path_to_item): @@ -1253,11 +1164,11 @@ def deserialize_primitive(data, klass, path_to_item): # The string should be in iso8601 datetime format. parsed_datetime = parse(data) date_only = ( - parsed_datetime.hour == 0 and - parsed_datetime.minute == 0 and - parsed_datetime.second == 0 and - parsed_datetime.tzinfo is None and - 8 <= len(data) <= 10 + parsed_datetime.hour == 0 + and parsed_datetime.minute == 0 + and parsed_datetime.second == 0 + and parsed_datetime.tzinfo is None + and 8 <= len(data) <= 10 ) if date_only: raise ValueError("This is a date, not a datetime") @@ -1271,21 +1182,17 @@ def deserialize_primitive(data, klass, path_to_item): if isinstance(data, str) and klass == float: if str(converted_value) != data: # '7' -> 7.0 -> '7.0' != '7' - raise ValueError('This is not a float') + raise ValueError("This is not a float") return converted_value except (OverflowError, ValueError) as ex: # parse can raise OverflowError raise ApiValueError( - "{0}Failed to parse {1} as {2}".format( - additional_message, repr(data), klass.__name__ - ), - path_to_item=path_to_item + "{0}Failed to parse {1} as {2}".format(additional_message, repr(data), klass.__name__), + path_to_item=path_to_item, ) from ex -def get_discriminator_class(model_class, - discr_name, - discr_value, cls_visited): +def get_discriminator_class(model_class, discr_name, discr_value, cls_visited): """Returns the child class specified by the discriminator. Args: @@ -1321,22 +1228,21 @@ def get_discriminator_class(model_class, # Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig # if we try to make BasquePig from mammal, we need to travel through # the oneOf descendant discriminators to find BasquePig - descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \ - model_class._composed_schemas.get('anyOf', ()) - ancestor_classes = model_class._composed_schemas.get('allOf', ()) + descendant_classes = model_class._composed_schemas.get("oneOf", ()) + model_class._composed_schemas.get( + "anyOf", () + ) + ancestor_classes = model_class._composed_schemas.get("allOf", ()) possible_classes = descendant_classes + ancestor_classes for cls in possible_classes: # Check if the schema has inherited discriminators. - if hasattr(cls, 'discriminator') and cls.discriminator is not None: - used_model_class = get_discriminator_class( - cls, discr_name, discr_value, cls_visited) + if hasattr(cls, "discriminator") and cls.discriminator is not None: + used_model_class = get_discriminator_class(cls, discr_name, discr_value, cls_visited) if used_model_class is not None: return used_model_class return used_model_class -def deserialize_model(model_data, model_class, path_to_item, check_type, - configuration, spec_property_naming): +def deserialize_model(model_data, model_class, path_to_item, check_type, configuration, spec_property_naming): """Deserializes model_data to model instance. Args: @@ -1360,10 +1266,12 @@ def deserialize_model(model_data, model_class, path_to_item, check_type, ApiKeyError """ - kw_args = dict(_check_type=check_type, - _path_to_item=path_to_item, - _configuration=configuration, - _spec_property_naming=spec_property_naming) + kw_args = dict( + _check_type=check_type, + _path_to_item=path_to_item, + _configuration=configuration, + _spec_property_naming=spec_property_naming, + ) if issubclass(model_class, ModelSimple): return model_class._new_from_openapi_data(model_data, **kw_args) @@ -1399,23 +1307,29 @@ def deserialize_file(response_data, configuration, content_disposition=None): os.remove(path) if content_disposition: - filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', - content_disposition).group(1) + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition).group(1) path = os.path.join(os.path.dirname(path), filename) with open(path, "wb") as f: if isinstance(response_data, str): # change str to bytes so we can write it - response_data = response_data.encode('utf-8') + response_data = response_data.encode("utf-8") f.write(response_data) f = open(path, "rb") return f -def attempt_convert_item(input_value, valid_classes, path_to_item, - configuration, spec_property_naming, key_type=False, - must_convert=False, check_type=True): +def attempt_convert_item( + input_value, + valid_classes, + path_to_item, + configuration, + spec_property_naming, + key_type=False, + must_convert=False, + check_type=True, +): """ Args: input_value (any): the data to convert @@ -1440,24 +1354,21 @@ def attempt_convert_item(input_value, valid_classes, path_to_item, ApiKeyError """ valid_classes_ordered = order_response_types(valid_classes) - valid_classes_coercible = remove_uncoercible( - valid_classes_ordered, input_value, spec_property_naming) + valid_classes_coercible = remove_uncoercible(valid_classes_ordered, input_value, spec_property_naming) if not valid_classes_coercible or key_type: # we do not handle keytype errors, json will take care # of this for us if configuration is None or not configuration.discard_unknown_keys: - raise get_type_error(input_value, path_to_item, valid_classes, - key_type=key_type) + raise get_type_error(input_value, path_to_item, valid_classes, key_type=key_type) for valid_class in valid_classes_coercible: try: if issubclass(valid_class, OpenApiModel): - return deserialize_model(input_value, valid_class, - path_to_item, check_type, - configuration, spec_property_naming) + return deserialize_model( + input_value, valid_class, path_to_item, check_type, configuration, spec_property_naming + ) elif valid_class == file_type: return deserialize_file(input_value, configuration) - return deserialize_primitive(input_value, valid_class, - path_to_item) + return deserialize_primitive(input_value, valid_class, path_to_item) except (ApiTypeError, ApiValueError, ApiKeyError) as conversion_exc: if must_convert: raise conversion_exc @@ -1489,10 +1400,12 @@ def is_type_nullable(input_type): return True if issubclass(input_type, ModelComposed): # If oneOf/anyOf, check if the 'null' type is one of the allowed types. - for t in input_type._composed_schemas.get('oneOf', ()): - if is_type_nullable(t): return True - for t in input_type._composed_schemas.get('anyOf', ()): - if is_type_nullable(t): return True + for t in input_type._composed_schemas.get("oneOf", ()): + if is_type_nullable(t): + return True + for t in input_type._composed_schemas.get("anyOf", ()): + if is_type_nullable(t): + return True return False @@ -1506,13 +1419,20 @@ def is_valid_type(input_class_simple, valid_classes): Returns: bool """ - if issubclass(input_class_simple, OpenApiModel) and \ - valid_classes == (bool, date, datetime, dict, float, int, list, str, none_type,): + if issubclass(input_class_simple, OpenApiModel) and valid_classes == ( + bool, + date, + datetime, + dict, + float, + int, + list, + str, + none_type, + ): return True valid_type = input_class_simple in valid_classes - if not valid_type and ( - issubclass(input_class_simple, OpenApiModel) or - input_class_simple is none_type): + if not valid_type and (issubclass(input_class_simple, OpenApiModel) or input_class_simple is none_type): for valid_class in valid_classes: if input_class_simple is none_type and is_type_nullable(valid_class): # Schema is oneOf/anyOf and the 'null' type is one of the allowed types. @@ -1520,17 +1440,16 @@ def is_valid_type(input_class_simple, valid_classes): if not (issubclass(valid_class, OpenApiModel) and valid_class.discriminator): continue discr_propertyname_py = list(valid_class.discriminator.keys())[0] - discriminator_classes = ( - valid_class.discriminator[discr_propertyname_py].values() - ) + discriminator_classes = valid_class.discriminator[discr_propertyname_py].values() valid_type = is_valid_type(input_class_simple, discriminator_classes) if valid_type: return True return valid_type -def validate_and_convert_types(input_value, required_types_mixed, path_to_item, - spec_property_naming, _check_type, configuration=None): +def validate_and_convert_types( + input_value, required_types_mixed, path_to_item, spec_property_naming, _check_type, configuration=None +): """Raises a TypeError is there is a problem, otherwise returns value Args: @@ -1575,18 +1494,18 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, spec_property_naming, key_type=False, must_convert=True, - check_type=_check_type + check_type=_check_type, ) return converted_instance else: - raise get_type_error(input_value, path_to_item, valid_classes, - key_type=False) + raise get_type_error(input_value, path_to_item, valid_classes, key_type=False) # input_value's type is in valid_classes if len(valid_classes) > 1 and configuration: # there are valid classes which are not the current class valid_classes_coercible = remove_uncoercible( - valid_classes, input_value, spec_property_naming, must_convert=False) + valid_classes, input_value, spec_property_naming, must_convert=False + ) if valid_classes_coercible: converted_instance = attempt_convert_item( input_value, @@ -1596,7 +1515,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, spec_property_naming, key_type=False, must_convert=False, - check_type=_check_type + check_type=_check_type, ) return converted_instance @@ -1604,9 +1523,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, # all types are of the required types and there are no more inner # variables left to look at return input_value - inner_required_types = child_req_types_by_current_type.get( - type(input_value) - ) + inner_required_types = child_req_types_by_current_type.get(type(input_value)) if inner_required_types is None: # for this type, there are not more inner variables left to look at return input_value @@ -1623,7 +1540,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, inner_path, spec_property_naming, _check_type, - configuration=configuration + configuration=configuration, ) elif isinstance(input_value, dict): if input_value == {}: @@ -1633,15 +1550,14 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item, inner_path = list(path_to_item) inner_path.append(inner_key) if get_simple_class(inner_key) != str: - raise get_type_error(inner_key, inner_path, valid_classes, - key_type=True) + raise get_type_error(inner_key, inner_path, valid_classes, key_type=True) input_value[inner_key] = validate_and_convert_types( inner_val, inner_required_types, inner_path, spec_property_naming, _check_type, - configuration=configuration + configuration=configuration, ) return input_value @@ -1658,7 +1574,9 @@ def model_to_dict(model_instance, serialize=True): attribute_map """ result = {} - extract_item = lambda item: (item[0], model_to_dict(item[1], serialize=serialize)) if hasattr(item[1], '_data_store') else item + extract_item = ( + lambda item: (item[0], model_to_dict(item[1], serialize=serialize)) if hasattr(item[1], "_data_store") else item + ) model_instances = [model_instance] if model_instance._composed_schemas: @@ -1678,32 +1596,26 @@ def model_to_dict(model_instance, serialize=True): except KeyError: used_fallback_python_attribute_names.add(attr) if isinstance(value, list): - if not value: - # empty list or None - result[attr] = value - else: - res = [] - for v in value: - if isinstance(v, PRIMITIVE_TYPES) or v is None: - res.append(v) - elif isinstance(v, ModelSimple): - res.append(v.value) - elif isinstance(v, dict): - res.append(dict(map( - extract_item, - v.items() - ))) - else: - res.append(model_to_dict(v, serialize=serialize)) - result[attr] = res + if not value: + # empty list or None + result[attr] = value + else: + res = [] + for v in value: + if isinstance(v, PRIMITIVE_TYPES) or v is None: + res.append(v) + elif isinstance(v, ModelSimple): + res.append(v.value) + elif isinstance(v, dict): + res.append(dict(map(extract_item, v.items()))) + else: + res.append(model_to_dict(v, serialize=serialize)) + result[attr] = res elif isinstance(value, dict): - result[attr] = dict(map( - extract_item, - value.items() - )) + result[attr] = dict(map(extract_item, value.items())) elif isinstance(value, ModelSimple): result[attr] = value.value - elif hasattr(value, '_data_store'): + elif hasattr(value, "_data_store"): result[attr] = model_to_dict(value, serialize=serialize) else: result[attr] = value @@ -1721,8 +1633,7 @@ def model_to_dict(model_instance, serialize=True): return result -def type_error_message(var_value=None, var_name=None, valid_classes=None, - key_type=None): +def type_error_message(var_value=None, var_name=None, valid_classes=None, key_type=None): """ Keyword Args: var_value (any): the variable which has the type_error @@ -1733,30 +1644,26 @@ def type_error_message(var_value=None, var_name=None, valid_classes=None, True if it is a key in a dict False if our item is an item in a list """ - key_or_value = 'value' + key_or_value = "value" if key_type: - key_or_value = 'key' + key_or_value = "key" valid_classes_phrase = get_valid_classes_phrase(valid_classes) - msg = ( - "Invalid type for variable '{0}'. Required {1} type {2} and " - "passed type was {3}".format( - var_name, - key_or_value, - valid_classes_phrase, - type(var_value).__name__, - ) + msg = "Invalid type for variable '{0}'. Required {1} type {2} and " "passed type was {3}".format( + var_name, + key_or_value, + valid_classes_phrase, + type(var_value).__name__, ) return msg def get_valid_classes_phrase(input_classes): - """Returns a string phrase describing what types are allowed - """ + """Returns a string phrase describing what types are allowed""" all_classes = list(input_classes) all_classes = sorted(all_classes, key=lambda cls: cls.__name__) all_class_names = [cls.__name__ for cls in all_classes] if len(all_class_names) == 1: - return 'is {0}'.format(all_class_names[0]) + return "is {0}".format(all_class_names[0]) return "is one of [{0}]".format(", ".join(all_class_names)) @@ -1778,10 +1685,10 @@ def get_allof_instances(self, model_args, constant_args): composed_instances (list) """ composed_instances = [] - for allof_class in self._composed_schemas['allOf']: + for allof_class in self._composed_schemas["allOf"]: try: - if constant_args.get('_spec_property_naming'): + if constant_args.get("_spec_property_naming"): allof_instance = allof_class._from_openapi_data(**model_args, **constant_args) else: allof_instance = allof_class(**model_args, **constant_args) @@ -1790,12 +1697,7 @@ def get_allof_instances(self, model_args, constant_args): raise ApiValueError( "Invalid inputs given to generate an instance of '%s'. The " "input data was invalid for the allOf schema '%s' in the composed " - "schema '%s'. Error=%s" % ( - allof_class.__name__, - allof_class.__name__, - self.__class__.__name__, - str(ex) - ) + "schema '%s'. Error=%s" % (allof_class.__name__, allof_class.__name__, self.__class__.__name__, str(ex)) ) from ex return composed_instances @@ -1828,13 +1730,13 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): Returns oneof_instance (instance) """ - if len(cls._composed_schemas['oneOf']) == 0: + if len(cls._composed_schemas["oneOf"]) == 0: return None oneof_instances = [] # Iterate over each oneOf schema and determine if the input data # matches the oneOf schemas. - for oneof_class in cls._composed_schemas['oneOf']: + for oneof_class in cls._composed_schemas["oneOf"]: # The composed oneOf schema allows the 'null' type and the input data # is the null value. This is a OAS >= 3.1 feature. if oneof_class is none_type: @@ -1846,13 +1748,13 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): try: if not single_value_input: - if constant_kwargs.get('_spec_property_naming'): + if constant_kwargs.get("_spec_property_naming"): oneof_instance = oneof_class._from_openapi_data(**model_kwargs, **constant_kwargs) else: oneof_instance = oneof_class(**model_kwargs, **constant_kwargs) else: if issubclass(oneof_class, ModelSimple): - if constant_kwargs.get('_spec_property_naming'): + if constant_kwargs.get("_spec_property_naming"): oneof_instance = oneof_class._from_openapi_data(model_arg, **constant_kwargs) else: oneof_instance = oneof_class(model_arg, **constant_kwargs) @@ -1860,10 +1762,10 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): oneof_instance = validate_and_convert_types( model_arg, (oneof_class,), - constant_kwargs['_path_to_item'], - constant_kwargs['_spec_property_naming'], - constant_kwargs['_check_type'], - configuration=constant_kwargs['_configuration'] + constant_kwargs["_path_to_item"], + constant_kwargs["_spec_property_naming"], + constant_kwargs["_check_type"], + configuration=constant_kwargs["_configuration"], ) oneof_instances.append(oneof_instance) except Exception: @@ -1871,14 +1773,12 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): if len(oneof_instances) == 0: raise ApiValueError( "Invalid inputs given to generate an instance of %s. None " - "of the oneOf schemas matched the input data." % - cls.__name__ + "of the oneOf schemas matched the input data." % cls.__name__ ) elif len(oneof_instances) > 1: raise ApiValueError( "Invalid inputs given to generate an instance of %s. Multiple " - "oneOf schemas matched the inputs, but a max of one is allowed." % - cls.__name__ + "oneOf schemas matched the inputs, but a max of one is allowed." % cls.__name__ ) return oneof_instances[0] @@ -1898,10 +1798,10 @@ def get_anyof_instances(self, model_args, constant_args): anyof_instances (list) """ anyof_instances = [] - if len(self._composed_schemas['anyOf']) == 0: + if len(self._composed_schemas["anyOf"]) == 0: return anyof_instances - for anyof_class in self._composed_schemas['anyOf']: + for anyof_class in self._composed_schemas["anyOf"]: # The composed oneOf schema allows the 'null' type and the input data # is the null value. This is a OAS >= 3.1 feature. if anyof_class is none_type: @@ -1910,7 +1810,7 @@ def get_anyof_instances(self, model_args, constant_args): continue try: - if constant_args.get('_spec_property_naming'): + if constant_args.get("_spec_property_naming"): anyof_instance = anyof_class._from_openapi_data(**model_args, **constant_args) else: anyof_instance = anyof_class(**model_args, **constant_args) @@ -1920,8 +1820,7 @@ def get_anyof_instances(self, model_args, constant_args): if len(anyof_instances) == 0: raise ApiValueError( "Invalid inputs given to generate an instance of %s. None of the " - "anyOf schemas matched the inputs." % - self.__class__.__name__ + "anyOf schemas matched the inputs." % self.__class__.__name__ ) return anyof_instances @@ -1935,7 +1834,7 @@ def get_discarded_args(self, composed_instances, model_args): # arguments passed to self were already converted to python names # before __init__ was called for instance in composed_instances: - if instance.__class__ in self._composed_schemas['allOf']: + if instance.__class__ in self._composed_schemas["allOf"]: try: keys = instance.to_dict().keys() discarded_keys = model_args - keys @@ -2030,9 +1929,4 @@ def validate_get_composed_info(constant_args, model_args, self): if prop_name not in discarded_args: var_name_to_model_instances[prop_name] = [self] + composed_instances - return [ - composed_instances, - var_name_to_model_instances, - additional_properties_model_instances, - discarded_args - ] + return [composed_instances, var_name_to_model_instances, additional_properties_model_instances, discarded_args] diff --git a/generated/openapi_client/rest.py b/generated/openapi_client/rest.py index 6654aeff..553e6ae0 100644 --- a/generated/openapi_client/rest.py +++ b/generated/openapi_client/rest.py @@ -20,14 +20,20 @@ import urllib3 import ipaddress -from openapi_client.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError +from openapi_client.exceptions import ( + ApiException, + UnauthorizedException, + ForbiddenException, + NotFoundException, + ServiceException, + ApiValueError, +) logger = logging.getLogger(__name__) class RESTResponse(io.IOBase): - def __init__(self, resp): self.urllib3_response = resp self.status = resp.status @@ -44,7 +50,6 @@ def getheader(self, name, default=None): class RESTClientObject(object): - def __init__(self, configuration, pools_size=4, maxsize=None): # urllib3.PoolManager will pass all kw parameters to connectionpool # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 @@ -60,13 +65,13 @@ def __init__(self, configuration, pools_size=4, maxsize=None): addition_pool_args = {} if configuration.assert_hostname is not None: - addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + addition_pool_args["assert_hostname"] = configuration.assert_hostname # noqa: E501 if configuration.retries is not None: - addition_pool_args['retries'] = configuration.retries + addition_pool_args["retries"] = configuration.retries if configuration.socket_options is not None: - addition_pool_args['socket_options'] = configuration.socket_options + addition_pool_args["socket_options"] = configuration.socket_options if maxsize is None: if configuration.connection_pool_maxsize is not None: @@ -75,7 +80,7 @@ def __init__(self, configuration, pools_size=4, maxsize=None): maxsize = 4 # https pool manager - if configuration.proxy and not should_bypass_proxies(configuration.host, no_proxy=configuration.no_proxy or ''): + if configuration.proxy and not should_bypass_proxies(configuration.host, no_proxy=configuration.no_proxy or ""): self.pool_manager = urllib3.ProxyManager( num_pools=pools_size, maxsize=maxsize, @@ -98,9 +103,17 @@ def __init__(self, configuration, pools_size=4, maxsize=None): **addition_pool_args ) - def request(self, method, url, query_params=None, headers=None, - body=None, post_params=None, _preload_content=True, - _request_timeout=None): + def request( + self, + method, + url, + query_params=None, + headers=None, + body=None, + post_params=None, + _preload_content=True, + _request_timeout=None, + ): """Perform requests. :param method: http request method @@ -120,13 +133,10 @@ def request(self, method, url, query_params=None, headers=None, (connection, read) timeouts. """ method = method.upper() - assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', - 'PATCH', 'OPTIONS'] + assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"] if post_params and body: - raise ApiValueError( - "body parameter cannot be used with post_params parameter." - ) + raise ApiValueError("body parameter cannot be used with post_params parameter.") post_params = post_params or {} headers = headers or {} @@ -135,60 +145,66 @@ def request(self, method, url, query_params=None, headers=None, if _request_timeout: if isinstance(_request_timeout, (int, float)): # noqa: E501,F821 timeout = urllib3.Timeout(total=_request_timeout) - elif (isinstance(_request_timeout, tuple) and - len(_request_timeout) == 2): - timeout = urllib3.Timeout( - connect=_request_timeout[0], read=_request_timeout[1]) + elif isinstance(_request_timeout, tuple) and len(_request_timeout) == 2: + timeout = urllib3.Timeout(connect=_request_timeout[0], read=_request_timeout[1]) try: # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` - if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]: # Only set a default Content-Type for POST, PUT, PATCH and OPTIONS requests - if (method != 'DELETE') and ('Content-Type' not in headers): - headers['Content-Type'] = 'application/json' + if (method != "DELETE") and ("Content-Type" not in headers): + headers["Content-Type"] = "application/json" if query_params: - url += '?' + urlencode(query_params) - if ('Content-Type' not in headers) or (re.search('json', headers['Content-Type'], re.IGNORECASE)): + url += "?" + urlencode(query_params) + if ("Content-Type" not in headers) or (re.search("json", headers["Content-Type"], re.IGNORECASE)): request_body = None if body is not None: request_body = json.dumps(body) r = self.pool_manager.request( - method, url, + method, + url, body=request_body, preload_content=_preload_content, timeout=timeout, - headers=headers) - elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + headers=headers, + ) + elif headers["Content-Type"] == "application/x-www-form-urlencoded": # noqa: E501 r = self.pool_manager.request( - method, url, + method, + url, fields=post_params, encode_multipart=False, preload_content=_preload_content, timeout=timeout, - headers=headers) - elif headers['Content-Type'] == 'multipart/form-data': + headers=headers, + ) + elif headers["Content-Type"] == "multipart/form-data": # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. - del headers['Content-Type'] + del headers["Content-Type"] r = self.pool_manager.request( - method, url, + method, + url, fields=post_params, encode_multipart=True, preload_content=_preload_content, timeout=timeout, - headers=headers) + headers=headers, + ) # Pass a `string` parameter directly in the body to support # other content types than Json when `body` argument is # provided in serialized form elif isinstance(body, str) or isinstance(body, bytes): request_body = body r = self.pool_manager.request( - method, url, + method, + url, body=request_body, preload_content=_preload_content, timeout=timeout, - headers=headers) + headers=headers, + ) else: # Cannot generate the request from given parameters msg = """Cannot prepare a request message for provided @@ -197,11 +213,9 @@ def request(self, method, url, query_params=None, headers=None, raise ApiException(status=0, reason=msg) # For `GET`, `HEAD` else: - r = self.pool_manager.request(method, url, - fields=query_params, - preload_content=_preload_content, - timeout=timeout, - headers=headers) + r = self.pool_manager.request( + method, url, fields=query_params, preload_content=_preload_content, timeout=timeout, headers=headers + ) except urllib3.exceptions.SSLError as e: msg = "{0}\n{1}".format(type(e).__name__, str(e)) raise ApiException(status=0, reason=msg) @@ -229,84 +243,134 @@ def request(self, method, url, query_params=None, headers=None, return r - def GET(self, url, headers=None, query_params=None, _preload_content=True, - _request_timeout=None): - return self.request("GET", url, - headers=headers, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - query_params=query_params) - - def HEAD(self, url, headers=None, query_params=None, _preload_content=True, - _request_timeout=None): - return self.request("HEAD", url, - headers=headers, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - query_params=query_params) - - def OPTIONS(self, url, headers=None, query_params=None, post_params=None, - body=None, _preload_content=True, _request_timeout=None): - return self.request("OPTIONS", url, - headers=headers, - query_params=query_params, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) - - def DELETE(self, url, headers=None, query_params=None, body=None, - _preload_content=True, _request_timeout=None): - return self.request("DELETE", url, - headers=headers, - query_params=query_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) - - def POST(self, url, headers=None, query_params=None, post_params=None, - body=None, _preload_content=True, _request_timeout=None): - return self.request("POST", url, - headers=headers, - query_params=query_params, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) - - def PUT(self, url, headers=None, query_params=None, post_params=None, - body=None, _preload_content=True, _request_timeout=None): - return self.request("PUT", url, - headers=headers, - query_params=query_params, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) - - def PATCH(self, url, headers=None, query_params=None, post_params=None, - body=None, _preload_content=True, _request_timeout=None): - return self.request("PATCH", url, - headers=headers, - query_params=query_params, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - body=body) + def GET(self, url, headers=None, query_params=None, _preload_content=True, _request_timeout=None): + return self.request( + "GET", + url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params, + ) + + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, _request_timeout=None): + return self.request( + "HEAD", + url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params, + ) + + def OPTIONS( + self, + url, + headers=None, + query_params=None, + post_params=None, + body=None, + _preload_content=True, + _request_timeout=None, + ): + return self.request( + "OPTIONS", + url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) + + def DELETE(self, url, headers=None, query_params=None, body=None, _preload_content=True, _request_timeout=None): + return self.request( + "DELETE", + url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) + + def POST( + self, + url, + headers=None, + query_params=None, + post_params=None, + body=None, + _preload_content=True, + _request_timeout=None, + ): + return self.request( + "POST", + url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) + + def PUT( + self, + url, + headers=None, + query_params=None, + post_params=None, + body=None, + _preload_content=True, + _request_timeout=None, + ): + return self.request( + "PUT", + url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) + + def PATCH( + self, + url, + headers=None, + query_params=None, + post_params=None, + body=None, + _preload_content=True, + _request_timeout=None, + ): + return self.request( + "PATCH", + url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body, + ) + # end of class RESTClientObject def is_ipv4(target): - """ Test if IPv4 address or not - """ + """Test if IPv4 address or not""" try: - chk = ipaddress.IPv4Address(target) - return True + chk = ipaddress.IPv4Address(target) + return True except ipaddress.AddressValueError: - return False + return False + def in_ipv4net(target, net): - """ Test if target belongs to given IPv4 network - """ + """Test if target belongs to given IPv4 network""" try: nw = ipaddress.IPv4Network(net) ip = ipaddress.IPv4Address(target) @@ -318,30 +382,29 @@ def in_ipv4net(target, net): except ipaddress.NetmaskValueError: return False + def should_bypass_proxies(url, no_proxy=None): - """ Yet another requests.should_bypass_proxies + """Yet another requests.should_bypass_proxies Test if proxies should not be used for a particular url. """ parsed = urlparse(url) # special cases - if parsed.hostname in [None, '']: + if parsed.hostname in [None, ""]: return True # special cases - if no_proxy in [None , '']: + if no_proxy in [None, ""]: return False - if no_proxy == '*': + if no_proxy == "*": return True - no_proxy = no_proxy.lower().replace(' ',''); - entries = ( - host for host in no_proxy.split(',') if host - ) + no_proxy = no_proxy.lower().replace(" ", "") + entries = (host for host in no_proxy.split(",") if host) if is_ipv4(parsed.hostname): for item in entries: - if in_ipv4net(parsed.hostname, item): - return True - return proxy_bypass_environment(parsed.hostname, {'no': no_proxy} ) + if in_ipv4net(parsed.hostname, item): + return True + return proxy_bypass_environment(parsed.hostname, {"no": no_proxy}) diff --git a/generated/setup.py b/generated/setup.py index 49df444f..b064e62c 100644 --- a/generated/setup.py +++ b/generated/setup.py @@ -21,8 +21,8 @@ # http://pypi.python.org/pypi/setuptools REQUIRES = [ - "urllib3 >= 1.25.3", - "python-dateutil", + "urllib3 >= 1.25.3", + "python-dateutil", ] setup( @@ -39,5 +39,5 @@ include_package_data=True, long_description="""\ Ask visual queries. # noqa: E501 - """ + """, ) diff --git a/generated/test/test_classification_result.py b/generated/test/test_classification_result.py index 7428fee7..b225d429 100644 --- a/generated/test/test_classification_result.py +++ b/generated/test/test_classification_result.py @@ -32,5 +32,5 @@ def testClassificationResult(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_detector.py b/generated/test/test_detector.py index 36f1e50d..f6c6d33f 100644 --- a/generated/test/test_detector.py +++ b/generated/test/test_detector.py @@ -14,7 +14,8 @@ import openapi_client from openapi_client.model.detector_type_enum import DetectorTypeEnum -globals()['DetectorTypeEnum'] = DetectorTypeEnum + +globals()["DetectorTypeEnum"] = DetectorTypeEnum from openapi_client.model.detector import Detector @@ -34,5 +35,5 @@ def testDetector(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_detector_creation_input.py b/generated/test/test_detector_creation_input.py index 24ddcc01..5026e26a 100644 --- a/generated/test/test_detector_creation_input.py +++ b/generated/test/test_detector_creation_input.py @@ -32,5 +32,5 @@ def testDetectorCreationInput(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_detector_type_enum.py b/generated/test/test_detector_type_enum.py index ab0814be..b42a568d 100644 --- a/generated/test/test_detector_type_enum.py +++ b/generated/test/test_detector_type_enum.py @@ -32,5 +32,5 @@ def testDetectorTypeEnum(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_detectors_api.py b/generated/test/test_detectors_api.py index 208ca86b..379218e2 100644 --- a/generated/test/test_detectors_api.py +++ b/generated/test/test_detectors_api.py @@ -25,23 +25,17 @@ def tearDown(self): pass def test_create_detector(self): - """Test case for create_detector - - """ + """Test case for create_detector""" pass def test_get_detector(self): - """Test case for get_detector - - """ + """Test case for get_detector""" pass def test_list_detectors(self): - """Test case for list_detectors - - """ + """Test case for list_detectors""" pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_image_queries_api.py b/generated/test/test_image_queries_api.py index e7189032..cea660a1 100644 --- a/generated/test/test_image_queries_api.py +++ b/generated/test/test_image_queries_api.py @@ -25,23 +25,17 @@ def tearDown(self): pass def test_get_image_query(self): - """Test case for get_image_query - - """ + """Test case for get_image_query""" pass def test_list_image_queries(self): - """Test case for list_image_queries - - """ + """Test case for list_image_queries""" pass def test_submit_image_query(self): - """Test case for submit_image_query - - """ + """Test case for submit_image_query""" pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_image_query.py b/generated/test/test_image_query.py index c404dd7a..57f6ae8e 100644 --- a/generated/test/test_image_query.py +++ b/generated/test/test_image_query.py @@ -16,9 +16,10 @@ from openapi_client.model.classification_result import ClassificationResult from openapi_client.model.image_query_type_enum import ImageQueryTypeEnum from openapi_client.model.result_type_enum import ResultTypeEnum -globals()['ClassificationResult'] = ClassificationResult -globals()['ImageQueryTypeEnum'] = ImageQueryTypeEnum -globals()['ResultTypeEnum'] = ResultTypeEnum + +globals()["ClassificationResult"] = ClassificationResult +globals()["ImageQueryTypeEnum"] = ImageQueryTypeEnum +globals()["ResultTypeEnum"] = ResultTypeEnum from openapi_client.model.image_query import ImageQuery @@ -38,5 +39,5 @@ def testImageQuery(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_image_query_type_enum.py b/generated/test/test_image_query_type_enum.py index 0997b4c7..acb68533 100644 --- a/generated/test/test_image_query_type_enum.py +++ b/generated/test/test_image_query_type_enum.py @@ -32,5 +32,5 @@ def testImageQueryTypeEnum(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_paginated_detector_list.py b/generated/test/test_paginated_detector_list.py index 63469643..2573b087 100644 --- a/generated/test/test_paginated_detector_list.py +++ b/generated/test/test_paginated_detector_list.py @@ -14,7 +14,8 @@ import openapi_client from openapi_client.model.detector import Detector -globals()['Detector'] = Detector + +globals()["Detector"] = Detector from openapi_client.model.paginated_detector_list import PaginatedDetectorList @@ -34,5 +35,5 @@ def testPaginatedDetectorList(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_paginated_image_query_list.py b/generated/test/test_paginated_image_query_list.py index de0a0f50..52e307e7 100644 --- a/generated/test/test_paginated_image_query_list.py +++ b/generated/test/test_paginated_image_query_list.py @@ -14,7 +14,8 @@ import openapi_client from openapi_client.model.image_query import ImageQuery -globals()['ImageQuery'] = ImageQuery + +globals()["ImageQuery"] = ImageQuery from openapi_client.model.paginated_image_query_list import PaginatedImageQueryList @@ -34,5 +35,5 @@ def testPaginatedImageQueryList(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/generated/test/test_result_type_enum.py b/generated/test/test_result_type_enum.py index b2fdc001..393152d7 100644 --- a/generated/test/test_result_type_enum.py +++ b/generated/test/test_result_type_enum.py @@ -32,5 +32,5 @@ def testResultTypeEnum(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/src/groundlight/client.py b/src/groundlight/client.py index be8f3006..fa75d6f0 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -63,13 +63,13 @@ def get_detector(self, id: str) -> Detector: return Detector.parse_obj(obj.to_dict()) def get_detector_by_name(self, name: str) -> Optional[Detector]: - #TODO: Do this on server. + # TODO: Do this on server. detector_list = self.list_detectors(page_size=100) for d in detector_list.results: if d.name == name: return d if detector_list.next: - #TODO: paginate + # TODO: paginate raise RuntimeError("You have too many detectors to use get_detector_by_name") return None @@ -90,8 +90,10 @@ def get_or_create_detector(self, name: str, query: str, config_name: str = None) if existing_detector.query == query: return existing_detector else: - raise ValueError(f"Found existing detector with {name=} (id={existing_detector.id}) but the queries don't match") - + raise ValueError( + f"Found existing detector with {name=} (id={existing_detector.id}) but the queries don't match" + ) + return self.create_detector(name, query, config_name) def get_image_query(self, id: str) -> ImageQuery: @@ -102,10 +104,11 @@ def list_image_queries(self, page: int = 1, page_size: int = 10) -> PaginatedIma obj = self.image_queries_api.list_image_queries(page=page, page_size=page_size) return PaginatedImageQueryList.parse_obj(obj.to_dict()) - def submit_image_query(self, - image: Union[str, bytes, BytesIO, BufferedReader, np.ndarray], - detector: Union[Detector, str], - ) -> ImageQuery: + def submit_image_query( + self, + image: Union[str, bytes, BytesIO, BufferedReader, np.ndarray], + detector: Union[Detector, str], + ) -> ImageQuery: """Evaluates an image with Groundlight. :param image: The image, in several possible formats: - a filename (string) of a jpeg file diff --git a/src/groundlight/images.py b/src/groundlight/images.py index f7ce9568..1ad47da8 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -17,11 +17,11 @@ def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: else: raise ValueError("We only support JPEG files, for now.") -def jpeg_from_numpy(img:np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: - """Converts a numpy array to BytesIO - """ - pilim = Image.fromarray(img.astype('uint8'), 'RGB') + +def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: + """Converts a numpy array to BytesIO""" + pilim = Image.fromarray(img.astype("uint8"), "RGB") with io.BytesIO() as buf: pilim.save(buf, "jpeg", quality=jpeg_quality) - #out = buf.getvalue() + # out = buf.getvalue() return out diff --git a/src/groundlight/numpy_optional.py b/src/groundlight/numpy_optional.py index c3c70adc..15dcea5f 100644 --- a/src/groundlight/numpy_optional.py +++ b/src/groundlight/numpy_optional.py @@ -5,28 +5,32 @@ look readable. """ + class UnavailableModule(object): def __init__(self, exc: Exception): self.exc = exc + def __getattr__(self, key): raise RuntimeError("attempt to use module that failed to load") from self.exc + class NumpyUnavailable(object): def __getattr__(self, key): raise RuntimeError("numpy is not installed") + try: import numpy + NUMPY_AVAILABLE = True except ImportError as e: numpy = UnavailableModule(e) NUMPY_AVAILABLE = False - + np = numpy -__all__ = ['np'] +__all__ = ["np"] if not NUMPY_AVAILABLE: # Put a few things in the namespace so downstream code looks normal np.ndarray = NumpyUnavailable() - From 9a1e7447e1005d4f3015f23ce935548fcc3a02c7 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 11:01:58 -0800 Subject: [PATCH 11/32] Making PIL optional also. --- src/groundlight/client.py | 2 +- src/groundlight/images.py | 4 +-- src/groundlight/numpy_optional.py | 36 ------------------------- src/groundlight/optional_imports.py | 41 +++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 40 deletions(-) delete mode 100644 src/groundlight/numpy_optional.py create mode 100644 src/groundlight/optional_imports.py diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 0a7105f5..b29af80e 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -9,7 +9,7 @@ from openapi_client.model.detector_creation_input import DetectorCreationInput from groundlight.images import buffer_from_jpeg_file, jpeg_from_numpy -from groundlight.numpy_optional import np +from groundlight.optional_imports import np API_TOKEN_WEB_URL = "https://app.groundlight.ai/reef/my-account/api-tokens" API_TOKEN_VARIABLE_NAME = "GROUNDLIGHT_API_TOKEN" diff --git a/src/groundlight/images.py b/src/groundlight/images.py index 1ad47da8..e44b588b 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -1,9 +1,7 @@ import imghdr import io -from PIL import Image - -from groundlight.numpy_optional import np +from groundlight.optional_imports import np, Image def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: diff --git a/src/groundlight/numpy_optional.py b/src/groundlight/numpy_optional.py deleted file mode 100644 index 15dcea5f..00000000 --- a/src/groundlight/numpy_optional.py +++ /dev/null @@ -1,36 +0,0 @@ -"""A shim that checks if numpy is installed and makes parts of it -available if it is, otherwise fails explicitly. -This can be confusing, but hopefully the errors are explicit enough to be -clear about what's happening, and it makes the code which hopes numpy is installed -look readable. -""" - - -class UnavailableModule(object): - def __init__(self, exc: Exception): - self.exc = exc - - def __getattr__(self, key): - raise RuntimeError("attempt to use module that failed to load") from self.exc - - -class NumpyUnavailable(object): - def __getattr__(self, key): - raise RuntimeError("numpy is not installed") - - -try: - import numpy - - NUMPY_AVAILABLE = True -except ImportError as e: - numpy = UnavailableModule(e) - NUMPY_AVAILABLE = False - -np = numpy - -__all__ = ["np"] - -if not NUMPY_AVAILABLE: - # Put a few things in the namespace so downstream code looks normal - np.ndarray = NumpyUnavailable() diff --git a/src/groundlight/optional_imports.py b/src/groundlight/optional_imports.py new file mode 100644 index 00000000..92d3044b --- /dev/null +++ b/src/groundlight/optional_imports.py @@ -0,0 +1,41 @@ +"""We use a trick to check if libraries like numpy are installed or not. +If they are, we make it available as normal. +If not, we set it up as a shim object which still lets type-hinting work properly, +but will fail at runtime if you try to use it. + +This can be confusing, but hopefully the errors are explicit enough to be +clear about what's happening, and it makes the code which hopes numpy is installed +look readable. +""" + + +class UnavailableModule(object): + """Represents a module that is not installed or otherwise unavailable at runtime. + Attempting to access anything in this object raises the original exception + (ImportError or similar) which happened when the optional library failed to import. + """ + + def __init__(self, exc: Exception): + self.exc = exc + + def __getattr__(self, key): + raise RuntimeError("attempt to use module that failed to load") from self.exc + + +try: + import numpy as np +except ImportError as e: + np = UnavailableModule(e) + # Expose np.ndarray so type-hinting looks normal + np.ndarray = np + +try: + import PIL + + Image = PIL.Image +except ImportError as e: + PIL = UnavailableModule(e) + Image = PIL + + +__all__ = ["np", "PIL", "Image"] From 098d0a8687534de4ea0f8f1b440ea97445abb39c Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 11:33:02 -0800 Subject: [PATCH 12/32] WIP fixing type hints --- .github/workflows/test-integ.yaml | 12 ++++++++++++ src/groundlight/optional_imports.py | 13 ++++++++----- test/unit/test_optional_imports.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 test/unit/test_optional_imports.py diff --git a/.github/workflows/test-integ.yaml b/.github/workflows/test-integ.yaml index 1f9d23c4..4b86e7ff 100644 --- a/.github/workflows/test-integ.yaml +++ b/.github/workflows/test-integ.yaml @@ -15,6 +15,8 @@ jobs: "3.10", "3.11", ] + install_numpy: [ true, false ] + install_pillow: [ true, false ] env: # This is associated with the "sdk-integ-test" user, credentials on 1password GROUNDLIGHT_API_TOKEN: ${{ secrets.GROUNDLIGHT_API_TOKEN }} @@ -32,5 +34,15 @@ jobs: pip install -U pip pip install poetry poetry install + - name: setup environment + run: make install + - name: install numpy + if: matrix.install_numpy + run: | + pip install numpy + - name: install pillow + if: matrix.install_pillow + run: | + pip install pillow - name: run tests run: make test-integ diff --git a/src/groundlight/optional_imports.py b/src/groundlight/optional_imports.py index 92d3044b..07be74a6 100644 --- a/src/groundlight/optional_imports.py +++ b/src/groundlight/optional_imports.py @@ -9,14 +9,18 @@ """ -class UnavailableModule(object): +class UnavailableModule(type): """Represents a module that is not installed or otherwise unavailable at runtime. Attempting to access anything in this object raises the original exception (ImportError or similar) which happened when the optional library failed to import. + + Needs to subclass type so that it works for type-hinting. """ - def __init__(self, exc: Exception): - self.exc = exc + def __new__(cls, exc): + out = type("UnavailableModule", (), {}) + out.exc = exc + return out def __getattr__(self, key): raise RuntimeError("attempt to use module that failed to load") from self.exc @@ -31,8 +35,7 @@ def __getattr__(self, key): try: import PIL - - Image = PIL.Image + from PIL import Image except ImportError as e: PIL = UnavailableModule(e) Image = PIL diff --git a/test/unit/test_optional_imports.py b/test/unit/test_optional_imports.py new file mode 100644 index 00000000..598a5238 --- /dev/null +++ b/test/unit/test_optional_imports.py @@ -0,0 +1,12 @@ +from typing import Union + +from groundlight.optional_imports import UnavailableModule + + +def test_type_hints(): + e = ModuleNotFoundError("perfect_perception module does not exist") + failed_import = UnavailableModule(e) + # Check that the UnavailableModule class can be used in type hints. + def typed_method(foo: Union[failed_import, str]): + print(foo) + From d8567398883b9a5e5ad5d6298ddc4b6832bac440 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 11:44:39 -0800 Subject: [PATCH 13/32] Adding some tests on the optional_import functionality --- src/groundlight/optional_imports.py | 1 + test/unit/test_optional_imports.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/groundlight/optional_imports.py b/src/groundlight/optional_imports.py index 07be74a6..d89de296 100644 --- a/src/groundlight/optional_imports.py +++ b/src/groundlight/optional_imports.py @@ -23,6 +23,7 @@ def __new__(cls, exc): return out def __getattr__(self, key): + # TODO: This isn't getting called for some reason. raise RuntimeError("attempt to use module that failed to load") from self.exc diff --git a/test/unit/test_optional_imports.py b/test/unit/test_optional_imports.py index 598a5238..e738df5c 100644 --- a/test/unit/test_optional_imports.py +++ b/test/unit/test_optional_imports.py @@ -1,12 +1,25 @@ from typing import Union +import pytest + from groundlight.optional_imports import UnavailableModule -def test_type_hints(): +@pytest.fixture +def failed_import() -> type: e = ModuleNotFoundError("perfect_perception module does not exist") - failed_import = UnavailableModule(e) + return UnavailableModule(e) + +def test_type_hints(failed_import): # Check that the UnavailableModule class can be used in type hints. def typed_method(foo: Union[failed_import, str]): print(foo) + assert True, "Yay UnavailableModule can be used in a type hint" + +@pytest.mark.skip("Would be nice if this works, but it doesn't") +def test_raises_exception(failed_import): + with pytest.raises(RuntimeError): + failed_import.foo + + From 1f148df1ac185983ca13dbf3248b30a15cff31df Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:04:09 -0800 Subject: [PATCH 14/32] Tests numpy submit image functionality when possible. --- test/integration/test_groundlight.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index c53bedf0..658ea4b4 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -56,6 +56,7 @@ def test_get_detector(gl: Groundlight, detector: Detector): assert isinstance(_detector, Detector) + # @pytest.mark.skip(reason="We don't want to create a million detectors and image_queries") def test_submit_image_query(gl: Groundlight, detector: Detector): _image_query = gl.submit_image_query(detector=detector.id, image="test/assets/dog.jpeg") @@ -74,3 +75,26 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): _image_query = gl.get_image_query(id=image_query.id) assert str(_image_query) assert isinstance(_image_query, ImageQuery) + + + +try: + import numpy as np + NUMPY_MISSING = False +except ImportError: + NUMPY_MISSING = True + +try: + import PIL + PIL_MISSING = False +except ImportError: + PIL_MISSING = True + +@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") +def test_submit_numpy_image(gl: Groundlight, detector: Detector): + np_img = np.random.uniform(0, 255, (600, 800, 3)) + _image_query = gl.submit_image_query(detector=detector.id, image=np_img) + assert str(_image_query) + assert isinstance(_image_query, ImageQuery) + + From 3765348bd3303b59b43e496524c56c0d35774d84 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Sat, 12 Nov 2022 20:05:20 +0000 Subject: [PATCH 15/32] Automatically reformatting code with black --- test/integration/test_groundlight.py | 7 +++---- test/unit/test_optional_imports.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 658ea4b4..2a7c5a72 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -56,7 +56,6 @@ def test_get_detector(gl: Groundlight, detector: Detector): assert isinstance(_detector, Detector) - # @pytest.mark.skip(reason="We don't want to create a million detectors and image_queries") def test_submit_image_query(gl: Groundlight, detector: Detector): _image_query = gl.submit_image_query(detector=detector.id, image="test/assets/dog.jpeg") @@ -77,24 +76,24 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): assert isinstance(_image_query, ImageQuery) - try: import numpy as np + NUMPY_MISSING = False except ImportError: NUMPY_MISSING = True try: import PIL + PIL_MISSING = False except ImportError: PIL_MISSING = True + @pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): np_img = np.random.uniform(0, 255, (600, 800, 3)) _image_query = gl.submit_image_query(detector=detector.id, image=np_img) assert str(_image_query) assert isinstance(_image_query, ImageQuery) - - diff --git a/test/unit/test_optional_imports.py b/test/unit/test_optional_imports.py index e738df5c..61e13166 100644 --- a/test/unit/test_optional_imports.py +++ b/test/unit/test_optional_imports.py @@ -10,10 +10,12 @@ def failed_import() -> type: e = ModuleNotFoundError("perfect_perception module does not exist") return UnavailableModule(e) + def test_type_hints(failed_import): # Check that the UnavailableModule class can be used in type hints. def typed_method(foo: Union[failed_import, str]): print(foo) + assert True, "Yay UnavailableModule can be used in a type hint" @@ -21,5 +23,3 @@ def typed_method(foo: Union[failed_import, str]): def test_raises_exception(failed_import): with pytest.raises(RuntimeError): failed_import.foo - - From 080858d63f9b1fdefca15ddca8c874c52051a970 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:09:14 -0800 Subject: [PATCH 16/32] See what happens if we try the numpy test all the time. --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 2a7c5a72..6b265222 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -91,7 +91,7 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): PIL_MISSING = True -@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") +#@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): np_img = np.random.uniform(0, 255, (600, 800, 3)) _image_query = gl.submit_image_query(detector=detector.id, image=np_img) From f1895d1d477d63bd72c4ca30e24085387b11c1bb Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:20:43 -0800 Subject: [PATCH 17/32] Don't fail fast so we can see what's happening across the matrix. --- .github/workflows/test-integ.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-integ.yaml b/.github/workflows/test-integ.yaml index 4b86e7ff..e413e123 100644 --- a/.github/workflows/test-integ.yaml +++ b/.github/workflows/test-integ.yaml @@ -5,7 +5,7 @@ jobs: run-tests: runs-on: ubuntu-20.04 strategy: - fail-fast: true + fail-fast: false matrix: python-version: [ #"3.6", # Default on Ubuntu18.04 but openapi-generator fails From 4f18886625fd999d34607d095bdd289951c6de06 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:21:16 -0800 Subject: [PATCH 18/32] black --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 6b265222..3f3da1ef 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -91,7 +91,7 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): PIL_MISSING = True -#@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") +# @pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): np_img = np.random.uniform(0, 255, (600, 800, 3)) _image_query = gl.submit_image_query(detector=detector.id, image=np_img) From d775494a561a8b73406b309a41d4797e522975ae Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:25:28 -0800 Subject: [PATCH 19/32] trying to get optional libraries to install properly in test matrix. --- .github/workflows/test-integ.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-integ.yaml b/.github/workflows/test-integ.yaml index e413e123..daff57b8 100644 --- a/.github/workflows/test-integ.yaml +++ b/.github/workflows/test-integ.yaml @@ -39,10 +39,10 @@ jobs: - name: install numpy if: matrix.install_numpy run: | - pip install numpy + poetry run pip install numpy - name: install pillow if: matrix.install_pillow run: | - pip install pillow + poetry run pip install pillow - name: run tests run: make test-integ From 4fe4ef4086e615dc84a209a37b5188e95dd82de5 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:39:37 -0800 Subject: [PATCH 20/32] fixing commented out error. --- src/groundlight/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/images.py b/src/groundlight/images.py index e44b588b..5210c02b 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -21,5 +21,5 @@ def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: pilim = Image.fromarray(img.astype("uint8"), "RGB") with io.BytesIO() as buf: pilim.save(buf, "jpeg", quality=jpeg_quality) - # out = buf.getvalue() + out = buf.getvalue() return out From bbb125e4b7c60cc5125a4516854839abde531e22 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:47:11 -0800 Subject: [PATCH 21/32] Writing some unit tests for numpy/jpeg functionality --- src/groundlight/images.py | 4 ++-- src/groundlight/optional_imports.py | 6 +++++- test/integration/test_groundlight.py | 16 +--------------- test/unit/test_imagefuncs.py | 20 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 test/unit/test_imagefuncs.py diff --git a/src/groundlight/images.py b/src/groundlight/images.py index 5210c02b..13121560 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -21,5 +21,5 @@ def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: pilim = Image.fromarray(img.astype("uint8"), "RGB") with io.BytesIO() as buf: pilim.save(buf, "jpeg", quality=jpeg_quality) - out = buf.getvalue() - return out + # out = buf.getvalue() # this gets bytes - not what we want + return buf diff --git a/src/groundlight/optional_imports.py b/src/groundlight/optional_imports.py index d89de296..106f7d54 100644 --- a/src/groundlight/optional_imports.py +++ b/src/groundlight/optional_imports.py @@ -29,17 +29,21 @@ def __getattr__(self, key): try: import numpy as np + MISSING_NUMPY = False except ImportError as e: np = UnavailableModule(e) # Expose np.ndarray so type-hinting looks normal np.ndarray = np + MISSING_NUMPY = True try: import PIL from PIL import Image + MISSING_PIL = False except ImportError as e: PIL = UnavailableModule(e) Image = PIL + MISSING_PIL = True -__all__ = ["np", "PIL", "Image"] +__all__ = ["np", "PIL", "Image", "MISSING_NUMPY", "MISSING_PIL"] diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 3f3da1ef..7a644b08 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -3,6 +3,7 @@ import pytest from groundlight import Groundlight +from groundlight.optional_imports import MISSING_NUMPY, MISSING_PIL from model import Detector, ImageQuery, PaginatedDetectorList, PaginatedImageQueryList @@ -76,21 +77,6 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): assert isinstance(_image_query, ImageQuery) -try: - import numpy as np - - NUMPY_MISSING = False -except ImportError: - NUMPY_MISSING = True - -try: - import PIL - - PIL_MISSING = False -except ImportError: - PIL_MISSING = True - - # @pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): np_img = np.random.uniform(0, 255, (600, 800, 3)) diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py new file mode 100644 index 00000000..6bb3b24d --- /dev/null +++ b/test/unit/test_imagefuncs.py @@ -0,0 +1,20 @@ +import pytest + +from groundlight.images import * + +def test_jpeg_from_numpy(): + np_img = np.random.uniform(0, 255, (480, 640, 3)) + stream = jpeg_from_numpy(np_img) + jpeg1 = stream.getvalue() + assert len(jpeg1) > 500 + + np_img = np.random.uniform(0, 255, (768, 1024, 3)) + stream = jpeg_from_numpy(np_img) + jpeg2 = stream.getvalue() + assert len(jpeg2) > len(jpeg1) + + np_img = np.random.uniform(0, 255, (768, 1024, 3)) + stream = jpeg_from_numpy(np_img, jpeg_quality=50) + jpeg3 = stream.getvalue() + assert len(jpeg2) > len(jpeg3) + From 5c2c547a0a4c8d336c59dab5cd97fc668bddf834 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:56:23 -0800 Subject: [PATCH 22/32] Fixing import --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 7a644b08..cd36ab23 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -3,7 +3,7 @@ import pytest from groundlight import Groundlight -from groundlight.optional_imports import MISSING_NUMPY, MISSING_PIL +from groundlight.optional_imports import * from model import Detector, ImageQuery, PaginatedDetectorList, PaginatedImageQueryList From 43e2f51779fcff411dfddea38ee7298251c8a2f5 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 12:59:46 -0800 Subject: [PATCH 23/32] Fixes closed buffer problem --- src/groundlight/images.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/groundlight/images.py b/src/groundlight/images.py index 13121560..21ad7c4b 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -19,7 +19,8 @@ def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: """Converts a numpy array to BytesIO""" pilim = Image.fromarray(img.astype("uint8"), "RGB") - with io.BytesIO() as buf: - pilim.save(buf, "jpeg", quality=jpeg_quality) - # out = buf.getvalue() # this gets bytes - not what we want - return buf + # don't use "with ... as buf:" because that closes it and makes it unreadable + buf = io.BytesIO() + pilim.save(buf, "jpeg", quality=jpeg_quality) + # out = buf.getvalue() # this gets bytes - not what we want + return buf From f2fb3e47a730a7962790c6190879355781d14dbd Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:00:09 -0800 Subject: [PATCH 24/32] black --- src/groundlight/optional_imports.py | 2 ++ test/unit/test_imagefuncs.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/groundlight/optional_imports.py b/src/groundlight/optional_imports.py index 106f7d54..ebf0e27b 100644 --- a/src/groundlight/optional_imports.py +++ b/src/groundlight/optional_imports.py @@ -29,6 +29,7 @@ def __getattr__(self, key): try: import numpy as np + MISSING_NUMPY = False except ImportError as e: np = UnavailableModule(e) @@ -39,6 +40,7 @@ def __getattr__(self, key): try: import PIL from PIL import Image + MISSING_PIL = False except ImportError as e: PIL = UnavailableModule(e) diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py index 6bb3b24d..4b65c9b9 100644 --- a/test/unit/test_imagefuncs.py +++ b/test/unit/test_imagefuncs.py @@ -2,12 +2,13 @@ from groundlight.images import * + def test_jpeg_from_numpy(): np_img = np.random.uniform(0, 255, (480, 640, 3)) stream = jpeg_from_numpy(np_img) jpeg1 = stream.getvalue() assert len(jpeg1) > 500 - + np_img = np.random.uniform(0, 255, (768, 1024, 3)) stream = jpeg_from_numpy(np_img) jpeg2 = stream.getvalue() @@ -17,4 +18,3 @@ def test_jpeg_from_numpy(): stream = jpeg_from_numpy(np_img, jpeg_quality=50) jpeg3 = stream.getvalue() assert len(jpeg2) > len(jpeg3) - From 7a2c1f4a326d169117a7c1013c55f3022e2a06a9 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:16:42 -0800 Subject: [PATCH 25/32] fixing bytesio / bytes messup --- src/groundlight/client.py | 2 +- src/groundlight/images.py | 12 ++++++------ test/unit/test_imagefuncs.py | 9 +++------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index b29af80e..46616e1a 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -131,7 +131,7 @@ def submit_image_query( # Already in the right format image_bytesio = image elif isinstance(image, np.ndarray): - image_bytesio = jpeg_from_numpy(image) + image_bytesio = BytesIO(jpeg_from_numpy(image)) else: raise TypeError( "Unsupported type for image. We only support numpy arrays (3,W,H) or JPEG images specified through a filename, bytes, BytesIO, or BufferedReader object." diff --git a/src/groundlight/images.py b/src/groundlight/images.py index 21ad7c4b..093c7bb4 100644 --- a/src/groundlight/images.py +++ b/src/groundlight/images.py @@ -16,11 +16,11 @@ def buffer_from_jpeg_file(image_filename: str) -> io.BufferedReader: raise ValueError("We only support JPEG files, for now.") -def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> io.BytesIO: +def jpeg_from_numpy(img: np.ndarray, jpeg_quality: int = 95) -> bytes: """Converts a numpy array to BytesIO""" pilim = Image.fromarray(img.astype("uint8"), "RGB") - # don't use "with ... as buf:" because that closes it and makes it unreadable - buf = io.BytesIO() - pilim.save(buf, "jpeg", quality=jpeg_quality) - # out = buf.getvalue() # this gets bytes - not what we want - return buf + with io.BytesIO() as buf: + buf = io.BytesIO() + pilim.save(buf, "jpeg", quality=jpeg_quality) + out = buf.getvalue() + return out diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py index 4b65c9b9..935b85fe 100644 --- a/test/unit/test_imagefuncs.py +++ b/test/unit/test_imagefuncs.py @@ -5,16 +5,13 @@ def test_jpeg_from_numpy(): np_img = np.random.uniform(0, 255, (480, 640, 3)) - stream = jpeg_from_numpy(np_img) - jpeg1 = stream.getvalue() + jpeg1 = jpeg_from_numpy(np_img) assert len(jpeg1) > 500 np_img = np.random.uniform(0, 255, (768, 1024, 3)) - stream = jpeg_from_numpy(np_img) - jpeg2 = stream.getvalue() + jpeg2 = jpeg_from_numpy(np_img) assert len(jpeg2) > len(jpeg1) np_img = np.random.uniform(0, 255, (768, 1024, 3)) - stream = jpeg_from_numpy(np_img, jpeg_quality=50) - jpeg3 = stream.getvalue() + jpeg3 = jpeg_from_numpy(np_img, jpeg_quality=50) assert len(jpeg2) > len(jpeg3) From 2fca73b2b8e15fa3e6edf4279bd797ddef0445b7 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:21:10 -0800 Subject: [PATCH 26/32] Test verification --- test/integration/test_groundlight.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index cd36ab23..8d6055c6 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -77,8 +77,9 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): assert isinstance(_image_query, ImageQuery) -# @pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") +@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): + assert False, "Need to see this fail so we know we're not always skipping" np_img = np.random.uniform(0, 255, (600, 800, 3)) _image_query = gl.submit_image_query(detector=detector.id, image=np_img) assert str(_image_query) From c5956b35668674dc67de2c2490d09237d4de7032 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:25:06 -0800 Subject: [PATCH 27/32] Fixing import check --- test/integration/test_groundlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 8d6055c6..670b5dda 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -77,7 +77,7 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): assert isinstance(_image_query, ImageQuery) -@pytest.mark.skipif(NUMPY_MISSING or PIL_MISSING, reason="Needs numpy and pillow") +@pytest.mark.skipif(MISSING_NUMPY or MISSING_PIL, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): assert False, "Need to see this fail so we know we're not always skipping" np_img = np.random.uniform(0, 255, (600, 800, 3)) From 124726239246838c9d320545f71355612c4b9988 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:33:23 -0800 Subject: [PATCH 28/32] Marking unpassable tests as skip --- test/unit/test_imagefuncs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py index 935b85fe..3a7d6114 100644 --- a/test/unit/test_imagefuncs.py +++ b/test/unit/test_imagefuncs.py @@ -1,8 +1,9 @@ import pytest from groundlight.images import * +from groundlight.optional_imports import * - +@pytest.mark.skipif(MISSING_NUMPY, reason="Needs numpy") def test_jpeg_from_numpy(): np_img = np.random.uniform(0, 255, (480, 640, 3)) jpeg1 = jpeg_from_numpy(np_img) From eeb2500f60c81ca3ca81d01c0cb850abeab68180 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:39:16 -0800 Subject: [PATCH 29/32] I think hope maybe we're done? --- .github/workflows/test-integ.yaml | 7 +++++++ test/integration/test_groundlight.py | 1 - test/unit/test_imagefuncs.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-integ.yaml b/.github/workflows/test-integ.yaml index daff57b8..67d4b860 100644 --- a/.github/workflows/test-integ.yaml +++ b/.github/workflows/test-integ.yaml @@ -5,6 +5,13 @@ jobs: run-tests: runs-on: ubuntu-20.04 strategy: + # It's totally debatable which is better here: fail-fast or not. + # Failing fast will use fewer cloud resources, in theory. + # But if the tests are slightly flaky (fail to pip install something) + # Then one flaky install kills lots of jobs that need to be redone. + # So the efficiency argument has its limits + # Failing slow is clearer about what's going on. + # This is pretty unambiguous, so we're going with it for now. fail-fast: false matrix: python-version: [ diff --git a/test/integration/test_groundlight.py b/test/integration/test_groundlight.py index 670b5dda..94718ac3 100644 --- a/test/integration/test_groundlight.py +++ b/test/integration/test_groundlight.py @@ -79,7 +79,6 @@ def test_get_image_query(gl: Groundlight, image_query: ImageQuery): @pytest.mark.skipif(MISSING_NUMPY or MISSING_PIL, reason="Needs numpy and pillow") def test_submit_numpy_image(gl: Groundlight, detector: Detector): - assert False, "Need to see this fail so we know we're not always skipping" np_img = np.random.uniform(0, 255, (600, 800, 3)) _image_query = gl.submit_image_query(detector=detector.id, image=np_img) assert str(_image_query) diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py index 3a7d6114..bdab8399 100644 --- a/test/unit/test_imagefuncs.py +++ b/test/unit/test_imagefuncs.py @@ -3,7 +3,7 @@ from groundlight.images import * from groundlight.optional_imports import * -@pytest.mark.skipif(MISSING_NUMPY, reason="Needs numpy") +@pytest.mark.skipif(MISSING_NUMPY or MISSING_PIL, reason="Needs numpy and pillow") def test_jpeg_from_numpy(): np_img = np.random.uniform(0, 255, (480, 640, 3)) jpeg1 = jpeg_from_numpy(np_img) From ff750136223296358966e0460755729bdbcc973b Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 13:39:36 -0800 Subject: [PATCH 30/32] black --- test/unit/test_imagefuncs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/test_imagefuncs.py b/test/unit/test_imagefuncs.py index bdab8399..3a75f6fa 100644 --- a/test/unit/test_imagefuncs.py +++ b/test/unit/test_imagefuncs.py @@ -3,6 +3,7 @@ from groundlight.images import * from groundlight.optional_imports import * + @pytest.mark.skipif(MISSING_NUMPY or MISSING_PIL, reason="Needs numpy and pillow") def test_jpeg_from_numpy(): np_img = np.random.uniform(0, 255, (480, 640, 3)) From 68440ceb7aa3bb5003897cb38191adb0856f0678 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Sat, 12 Nov 2022 14:13:36 -0800 Subject: [PATCH 31/32] just some comments on a failing test. --- test/unit/test_optional_imports.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/test_optional_imports.py b/test/unit/test_optional_imports.py index 61e13166..27964e0e 100644 --- a/test/unit/test_optional_imports.py +++ b/test/unit/test_optional_imports.py @@ -21,5 +21,9 @@ def typed_method(foo: Union[failed_import, str]): @pytest.mark.skip("Would be nice if this works, but it doesn't") def test_raises_exception(failed_import): + # We'd like the UnavailableModule object to raise an exception + # anytime you access it, where the exception is a RuntimeError + # but builds on the original ImportError so you can see what went wrong. + # The old version had this, but didn't work with modern type-hinting. with pytest.raises(RuntimeError): failed_import.foo From 32f824f020d52830233583ad069d11053b3ca1b1 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Mon, 14 Nov 2022 11:52:56 -0800 Subject: [PATCH 32/32] Bumping version to 0.6.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 061de78d..83b7b7a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "groundlight" -version = "0.5.4" +version = "0.6.1" license = "MIT" readme = "UserGuide.md" homepage = "https://groundlight.ai"