diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index d830f7187..6596a5315 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -23,7 +23,7 @@ jobs: if: "!contains(github.event.head_commit.message, 'skipci')" runs-on: ${{ matrix.os }} env: - USING_COVERAGE: "3.9" + USING_COVERAGE: "3.10" strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] @@ -34,9 +34,9 @@ jobs: uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: "3.10" - name: Install Poetry uses: snok/install-poetry@v1.3.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb6b95e38..fec48fbdb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: if: "github.event.pull_request.merged == true" runs-on: ${{ matrix.os }} env: - USING_COVERAGE: "3.9" + USING_COVERAGE: "3.10" strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] @@ -26,9 +26,9 @@ jobs: uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: "3.10" - name: Install Poetry uses: snok/install-poetry@v1.3.2 diff --git a/.github/workflows/version-compatibility.yml b/.github/workflows/version-compatibility.yml index d2ce366a5..19c094116 100644 --- a/.github/workflows/version-compatibility.yml +++ b/.github/workflows/version-compatibility.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 - name: Install Poetry uses: snok/install-poetry@v1 - name: Check version compatibility has been tested diff --git a/README.md b/README.md index 13067d622..7532c5619 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Documentation Status](https://readthedocs.org/projects/octue-python-sdk/badge/?version=latest)](https://octue-python-sdk.readthedocs.io/en/latest/?badge=latest) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) [![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10961975.svg)](https://doi.org/10.5281/zenodo.10961975) # Octue Python SDK Purple Fruit Snake @@ -15,7 +16,9 @@ Read the docs [here.](https://octue-python-sdk.readthedocs.io/en/latest/) Uses our [twined](https://twined.readthedocs.io/en/latest/) library for data validation. ## Installation and usage + To install, run one of: + ```shell pip install octue ``` @@ -25,6 +28,7 @@ poetry add octue ``` The command line interface (CLI) can then be accessed via: + ```shell octue --help ``` @@ -59,6 +63,7 @@ Commands: ``` ## Deprecated code + When code is deprecated, it will still work but a deprecation warning will be issued with a suggestion on how to update it. After an adjustment period, deprecations will be removed from the codebase according to the [code removal schedule](https://github.com/octue/octue-sdk-python/issues/415). This constitutes a breaking change. @@ -66,6 +71,7 @@ This constitutes a breaking change. ## Developer notes ### Installation + We use [Poetry](https://python-poetry.org/) as our package manager. For development, run the following from the repository root, which will editably install the package: @@ -76,18 +82,24 @@ poetry install --all-extras Then run the tests to check everything's working. ### Testing + These environment variables need to be set to run the tests: -* `GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service/account/file.json` -* `TEST_PROJECT_NAME=` + +- `GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service/account/file.json` +- `TEST_PROJECT_NAME=` Then, from the repository root, run + ```shell python3 -m unittest ``` + or + ```shell tox ``` ## Contributing + Take a look at our [contributing](/docs/contributing.md) page. diff --git a/docs/source/asking_questions.rst b/docs/source/asking_questions.rst index acc4b6448..36db95bc8 100644 --- a/docs/source/asking_questions.rst +++ b/docs/source/asking_questions.rst @@ -35,7 +35,7 @@ Asking a question backend={"name": "GCPPubSubBackend", "project_name": "my-project"}, ) - answer = child.ask( + answer, question_uuid = child.ask( input_values={"height": 32, "width": 3}, input_manifest=manifest, ) @@ -104,7 +104,6 @@ access the event store and run: **Options** - ``kind`` - Only retrieve this kind of event if present (e.g. "result") -- ``include_attributes`` - If ``True``, retrieve all the events' attributes as well - ``include_backend_metadata`` - If ``True``, retrieve information about the service backend that produced the event - ``limit`` - If set to a positive integer, limit the number of events returned to this @@ -232,7 +231,7 @@ this: .. code-block:: python - answer = analysis.children["elevation"].ask(input_values={"longitude": 0, "latitude": 1}) + answer, question_uuid = analysis.children["elevation"].ask(input_values={"longitude": 0, "latitude": 1}) if your app configuration file is: @@ -323,7 +322,7 @@ then you can override them like this: .. code-block:: python - answer = child.ask( + answer, question_uuid = child.ask( input_values={"height": 32, "width": 3}, children=[ { diff --git a/docs/source/manifest.rst b/docs/source/manifest.rst index 8d00af312..7a13fc808 100644 --- a/docs/source/manifest.rst +++ b/docs/source/manifest.rst @@ -49,7 +49,7 @@ Get an Octue service to analyse data for you as part of a larger analysis. backend={"name": "GCPPubSubBackend", "project_name": "my-project"}, ) - answer = child.ask(input_manifest=manifest) + answer, question_uuid = child.ask(input_manifest=manifest) See :doc:`here ` for more information. @@ -108,7 +108,7 @@ the cloud and then download them again for each service (as would happen with cl } ) - analysis.children["wind_speed"].ask( + answer, question_uuid = analysis.children["wind_speed"].ask( input_values=analysis.input_values, input_manifest=analysis.input_manifest, allow_local_files=True, diff --git a/docs/source/testing_services.rst b/docs/source/testing_services.rst index df2419124..cf3612922 100644 --- a/docs/source/testing_services.rst +++ b/docs/source/testing_services.rst @@ -87,7 +87,7 @@ Instantiating a child emulator in python def handle_monitor_message(message): ... - result = child_emulator.ask( + result, question_uuid = child_emulator.ask( input_values={"hello": "world"}, handle_monitor_message=handle_monitor_message, ) @@ -133,7 +133,7 @@ You can then instantiate a child emulator from this in python: def handle_monitor_message(message): ... - result = child_emulator.ask( + result, question_uuid = child_emulator.ask( input_values={"hello": "world"}, handle_monitor_message=handle_monitor_message, ) @@ -226,7 +226,7 @@ child. backend={"name": "GCPPubSubBackend", "project_name": "my-project"}, ) - result = child.ask(input_values=[1, 2, 3, 4]) + result, question_uuid = child.ask(input_values=[1, 2, 3, 4]) child.received_events >>> [ @@ -260,6 +260,6 @@ You can then feed these into a child emulator to emulate one possible response o child_emulator = ChildEmulator(events=child.received_events) child_emulator.ask(input_values=[1, 2, 3, 4]) - >>> {"some": "results"} + >>> {"some": "results"}, "9cab579f-c486-4324-ac9b-96491d26266b" You can also create test fixtures from :ref:`downloaded service crash diagnostics `. diff --git a/docs/source/troubleshooting_services.rst b/docs/source/troubleshooting_services.rst index e861a572d..f60ca5fde 100644 --- a/docs/source/troubleshooting_services.rst +++ b/docs/source/troubleshooting_services.rst @@ -121,7 +121,7 @@ For example: backend={"name": "GCPPubSubBackend", "project_name": "my-project"}, ) - answer = child.ask( + answer, question_uuid = child.ask( input_values={"height": 32, "width": 3}, save_diagnostics="SAVE_DIAGNOSTICS_OFF", ) diff --git a/octue/cloud/deployment/google/cloud_run/Dockerfile-python311 b/octue/cloud/deployment/google/cloud_run/Dockerfile-python311 index 2a34ef365..48203db1c 100644 --- a/octue/cloud/deployment/google/cloud_run/Dockerfile-python311 +++ b/octue/cloud/deployment/google/cloud_run/Dockerfile-python311 @@ -1,4 +1,4 @@ -FROM windpioneers/gdal-python:little-gecko-gdal-2.4.1-python-3.11-slim +FROM windpioneers/gdal-python:modest-heron-gdal-2.4.1-python-3.11-slim # Ensure print statements and log messages appear promptly in Cloud Logging. ENV PYTHONUNBUFFERED True diff --git a/octue/cloud/emulators/child.py b/octue/cloud/emulators/child.py index 7614c5fd8..7f210a1d6 100644 --- a/octue/cloud/emulators/child.py +++ b/octue/cloud/emulators/child.py @@ -125,12 +125,12 @@ def ask( :param bool asynchronous: if `True`, don't create an answer subscription :param float timeout: time in seconds to wait for an answer before raising a timeout error :raise TimeoutError: if the timeout is exceeded while waiting for an answer - :return dict: a dictionary containing the keys "output_values" and "output_manifest" + :return dict, str: a dictionary containing the keys "output_values" and "output_manifest", and the question UUID """ with ServicePatcher(): self._child.serve(allow_existing=True) - subscription, _ = self._parent.ask( + subscription, question_uuid = self._parent.ask( service_id=self._child.id, input_values=input_values, input_manifest=input_manifest, @@ -141,13 +141,15 @@ def ask( asynchronous=asynchronous, ) - return self._parent.wait_for_answer( + answer = self._parent.wait_for_answer( subscription, handle_monitor_message=handle_monitor_message, record_events=record_events, timeout=timeout, ) + return answer, question_uuid + def _emulate_analysis( self, analysis_id, diff --git a/octue/cloud/pub_sub/bigquery.py b/octue/cloud/pub_sub/bigquery.py index d02833c07..70b278b4d 100644 --- a/octue/cloud/pub_sub/bigquery.py +++ b/octue/cloud/pub_sub/bigquery.py @@ -3,53 +3,46 @@ from google.cloud.bigquery import Client, QueryJobConfig, ScalarQueryParameter from octue.cloud.events.validation import VALID_EVENT_KINDS +from octue.exceptions import ServiceNotFound +from octue.resources import Manifest -def get_events( - table_id, - sender, - question_uuid, - kind=None, - include_attributes=False, - include_backend_metadata=False, - limit=1000, -): +def get_events(table_id, sender, question_uuid, kind=None, include_backend_metadata=False, limit=1000): """Get Octue service events for a question from a sender from a Google BigQuery event store. :param str table_id: the full ID of the table e.g. "your-project.your-dataset.your-table" :param str sender: the SRUID of the sender of the events :param str question_uuid: the UUID of the question to get the events for :param str|None kind: the kind of event to get; if `None`, all event kinds are returned - :param bool include_attributes: if `True`, include events' attributes (excluding question UUID) :param bool include_backend_metadata: if `True`, include the service backend metadata :param int limit: the maximum number of events to return + :raise ValueError: if the `kind` parameter is invalid + :raise octue.exceptions.ServiceNotFound: if the sender hasn't emitted any events related to the question UUID (or any events at all) :return list(dict): the events for the question """ if kind: if kind not in VALID_EVENT_KINDS: raise ValueError(f"`kind` must be one of {VALID_EVENT_KINDS!r}; received {kind!r}.") - event_kind_condition = [f'AND JSON_EXTRACT_SCALAR(event, "$.kind") = "{kind}"'] + event_kind_condition = [f"AND kind={kind!r}"] else: event_kind_condition = [] client = Client() - fields = ["`event`"] - - if include_attributes: - fields.extend( - ( - "`datetime`", - "`uuid`", - "`originator`", - "`sender`", - "`sender_type`", - "`sender_sdk_version`", - "`recipient`", - "`order`", - "`other_attributes`", - ) - ) + + fields = [ + "`event`", + "`kind`", + "`datetime`", + "`uuid`", + "`originator`", + "`sender`", + "`sender_type`", + "`sender_sdk_version`", + "`recipient`", + "`order`", + "`other_attributes`", + ] if include_backend_metadata: fields.extend(("`backend`", "`backend_metadata`")) @@ -74,16 +67,64 @@ def get_events( ) query_job = client.query(query, job_config=job_config) - rows = query_job.result() - df = rows.to_dataframe() + result = query_job.result() + + if result.total_rows == 0: + raise ServiceNotFound( + f"No events found. The requested sender {sender!r} may not exist or it hasn't emitted any events for " + f"question {question_uuid!r} (or any events at all)." + ) + + df = result.to_dataframe() # Convert JSON strings to python primitives. df["event"] = df["event"].map(json.loads) - - if "other_attributes" in df: - df["other_attributes"] = df["other_attributes"].map(json.loads) + df["event"].apply(_deserialise_manifest_if_present) + df["other_attributes"] = df["other_attributes"].map(json.loads) if "backend_metadata" in df: df["backend_metadata"] = df["backend_metadata"].map(json.loads) - return df.to_dict(orient="records") + events = df.to_dict(orient="records") + return _unflatten_events(events) + + +def _deserialise_manifest_if_present(event): + """If the event is a "question" or "result" event and a manifest is present, deserialise the manifest and replace + the serialised manifest with it. + + :param dict event: an Octue service event + :return None: + """ + manifest_keys = {"input_manifest", "output_manifest"} + + for key in manifest_keys: + if key in event: + event[key] = Manifest.deserialise(event[key]) + # Only one of the manifest types will be in the event, so return if one is found. + return + + +def _unflatten_events(events): + """Convert the events and attributes from the flat structure of the BigQuery table into the nested structure of the + service communication schema. + + :param list(dict) events: flattened events + :return list(dict): unflattened events + """ + for event in events: + event["event"]["kind"] = event.pop("kind") + + event["attributes"] = { + "datetime": event.pop("datetime").isoformat(), + "uuid": event.pop("uuid"), + "originator": event.pop("originator"), + "sender": event.pop("sender"), + "sender_type": event.pop("sender_type"), + "sender_sdk_version": event.pop("sender_sdk_version"), + "recipient": event.pop("recipient"), + "order": event.pop("order"), + **event.pop("other_attributes"), + } + + return events diff --git a/octue/resources/child.py b/octue/resources/child.py index b0208472d..c0f30bd67 100644 --- a/octue/resources/child.py +++ b/octue/resources/child.py @@ -88,9 +88,9 @@ def ask( :param float timeout: time in seconds to wait for an answer before raising a timeout error :param float|int maximum_heartbeat_interval: the maximum amount of time (in seconds) allowed between child heartbeats before an error is raised :raise TimeoutError: if the timeout is exceeded while waiting for an answer - :return dict|octue.cloud.pub_sub.subscription.Subscription|None: for a synchronous question, a dictionary containing the keys "output_values" and "output_manifest" from the result; for a question with a push endpoint, the push subscription; for an asynchronous question, `None` + :return dict|octue.cloud.pub_sub.subscription.Subscription|None, str: for a synchronous question, a dictionary containing the keys "output_values" and "output_manifest" from the result, and the question UUID; for a question with a push endpoint, the push subscription and the question UUID; for an asynchronous question, `None` and the question UUID """ - subscription, _ = self._service.ask( + subscription, question_uuid = self._service.ask( service_id=self.id, input_values=input_values, input_manifest=input_manifest, @@ -105,9 +105,9 @@ def ask( ) if push_endpoint or asynchronous: - return subscription + return subscription, question_uuid - return self._service.wait_for_answer( + answer = self._service.wait_for_answer( subscription=subscription, handle_monitor_message=handle_monitor_message, record_events=record_events, @@ -115,6 +115,8 @@ def ask( maximum_heartbeat_interval=maximum_heartbeat_interval, ) + return answer, question_uuid + def ask_multiple(self, *questions, raise_errors=True, max_retries=0, prevent_retries_when=None, max_workers=None): """Ask the child multiple questions in parallel and wait for the answers. Each question should be provided as a dictionary of `Child.ask` keyword arguments. If `raise_errors` is `True`, an error is raised and no answers are @@ -128,7 +130,7 @@ def ask_multiple(self, *questions, raise_errors=True, max_retries=0, prevent_ret :param int|None max_workers: the maximum number of questions that can be asked at once; defaults to `min(32, os.cpu_count() + 4, len(questions))` (see `concurrent.futures.ThreadPoolExecutor`) :raise ValueError: if the maximum number of parallel questions is set too high :raise Exception: if any question raises an error if `raise_errors` is `True` - :return list: the answers or caught errors of the questions in the same order as asked + :return list(dict|Exception, str): the answers or caught errors of the questions, and the question UUIDs (in the same order as asked) """ prevent_retries_when = prevent_retries_when or [] diff --git a/octue/templates/template-child-services/parent_service/app.py b/octue/templates/template-child-services/parent_service/app.py index 426982e87..2d198c25b 100644 --- a/octue/templates/template-child-services/parent_service/app.py +++ b/octue/templates/template-child-services/parent_service/app.py @@ -16,8 +16,10 @@ def run(analysis): # Send input data to children specified in `app_configuration.json` and receive output data. The output comes as a # dictionary with an `output_values` key and an `output_manifest` key. - elevations = analysis.children["elevation"].ask(input_values=analysis.input_values, timeout=60)["output_values"] - wind_speeds = analysis.children["wind_speed"].ask(input_values=analysis.input_values, timeout=60)["output_values"] + elevations = analysis.children["elevation"].ask(input_values=analysis.input_values, timeout=60)[0]["output_values"] + wind_speeds = analysis.children["wind_speed"].ask(input_values=analysis.input_values, timeout=60)[0][ + "output_values" + ] logger.info( "The wind speeds and elevations at %s are %s and %s.", diff --git a/poetry.lock b/poetry.lock index e91eec3fe..029a2fccb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -505,7 +505,7 @@ langdetect = ["langdetect"] name = "db-dtypes" version = "1.2.0" description = "Pandas Data Types for SQL systems (BigQuery, Spanner)" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "db-dtypes-1.2.0.tar.gz", hash = "sha256:3531bb1fb8b5fbab33121fe243ccc2ade16ab2524f4c113b05cc702a1908e6ea"}, @@ -577,13 +577,13 @@ dates = ["pytz (>=2019.1)"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -679,26 +679,27 @@ google-crc32c = "1.3.0" [[package]] name = "google-api-core" -version = "2.17.1" +version = "2.18.0" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, - {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, + {file = "google-api-core-2.18.0.tar.gz", hash = "sha256:62d97417bfc674d6cef251e5c4d639a9655e00c45528c4364fbfebb478ce72a9"}, + {file = "google_api_core-2.18.0-py3-none-any.whl", hash = "sha256:5a63aa102e0049abe85b5b88cb9409234c1f70afcda21ce1e40b285b9629c1d6"}, ] [package.dependencies] google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] +proto-plus = ">=1.22.3,<2.0.0dev" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" @@ -709,13 +710,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.28.1" +version = "2.29.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.28.1.tar.gz", hash = "sha256:34fc3046c257cedcf1622fc4b31fc2be7923d9b4d44973d481125ecc50d83885"}, - {file = "google_auth-2.28.1-py2.py3-none-any.whl", hash = "sha256:25141e2d7a14bfcba945f5e9827f98092716e99482562f15306e5b026e21aa72"}, + {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, + {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, ] [package.dependencies] @@ -732,17 +733,18 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-cloud-bigquery" -version = "3.18.0" +version = "3.21.0" description = "Google BigQuery API client library" -optional = false +optional = true python-versions = ">=3.7" files = [ - {file = "google-cloud-bigquery-3.18.0.tar.gz", hash = "sha256:74f0fc6f0ba9477f808d25924dc8a052c55f7ca91064e83e16d3ee5fb7ca77ab"}, - {file = "google_cloud_bigquery-3.18.0-py2.py3-none-any.whl", hash = "sha256:3520552075502c69710d37b1e9600c84e6974ad271914677d16bfafa502857fb"}, + {file = "google-cloud-bigquery-3.21.0.tar.gz", hash = "sha256:6265c39f9d5bdf50f11cb81a9c2a0605d285df34ac139de0d2333b1250add0ff"}, + {file = "google_cloud_bigquery-3.21.0-py2.py3-none-any.whl", hash = "sha256:83a090aae16b3a687ef22e7b0a1b551e18da615b1c4855c5f312f198959e7739"}, ] [package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<3.0.0dev" google-cloud-core = ">=1.6.0,<3.0.0dev" google-resumable-media = ">=0.6.0,<3.0dev" packaging = ">=20.0.0" @@ -781,13 +783,13 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-pubsub" -version = "2.20.0" +version = "2.21.1" description = "Google Cloud Pub/Sub API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-pubsub-2.20.0.tar.gz", hash = "sha256:48c8e17a8168c43e3188635cbd9e07fbe3004120433712ce84b3a04bbf18c188"}, - {file = "google_cloud_pubsub-2.20.0-py2.py3-none-any.whl", hash = "sha256:8c69ed04800f4f552cdf3b9028f06d9271ac6e60443b2308c984def442e69684"}, + {file = "google-cloud-pubsub-2.21.1.tar.gz", hash = "sha256:31fcf07444b7f813a616c4b650e1fbf1dc998a088fe0059a76164855ac17f05c"}, + {file = "google_cloud_pubsub-2.21.1-py2.py3-none-any.whl", hash = "sha256:55a3602ec45bc09626604d712032288a8ee3566145cb83523cff908938f69a4b"}, ] [package.dependencies] @@ -797,8 +799,8 @@ grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -807,13 +809,13 @@ libcst = ["libcst (>=0.3.10)"] [[package]] name = "google-cloud-secret-manager" -version = "2.18.3" +version = "2.20.0" description = "Google Cloud Secret Manager API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-secret-manager-2.18.3.tar.gz", hash = "sha256:1db2f409324536e34f985081d389e3974ca3a3668df7845cad0be03ab8c0fa7d"}, - {file = "google_cloud_secret_manager-2.18.3-py2.py3-none-any.whl", hash = "sha256:4d4af82bddd9099ebdbe79e0c6b68f6c6cabea8323a3c1275bcead8f56310fb7"}, + {file = "google-cloud-secret-manager-2.20.0.tar.gz", hash = "sha256:a086a7413aaf4fffbd1c4fe9229ef0ce9bcf48f5a8df5b449c4a32deb5a2cfde"}, + {file = "google_cloud_secret_manager-2.20.0-py2.py3-none-any.whl", hash = "sha256:c20bf22e59d220c51aa84a1db3411b14b83aa71f788fae8d273c03a4bf3e77ed"}, ] [package.dependencies] @@ -825,13 +827,13 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 [[package]] name = "google-cloud-storage" -version = "2.15.0" +version = "2.16.0" description = "Google Cloud Storage API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-storage-2.15.0.tar.gz", hash = "sha256:7560a3c48a03d66c553dc55215d35883c680fe0ab44c23aa4832800ccc855c74"}, - {file = "google_cloud_storage-2.15.0-py2.py3-none-any.whl", hash = "sha256:5d9237f88b648e1d724a0f20b5cde65996a37fe51d75d17660b1404097327dd2"}, + {file = "google-cloud-storage-2.16.0.tar.gz", hash = "sha256:dda485fa503710a828d01246bd16ce9db0823dc51bbca742ce96a6817d58669f"}, + {file = "google_cloud_storage-2.16.0-py2.py3-none-any.whl", hash = "sha256:91a06b96fb79cf9cdfb4e759f178ce11ea885c79938f89590344d079305f5852"}, ] [package.dependencies] @@ -920,13 +922,13 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] [[package]] name = "googleapis-common-protos" -version = "1.62.0" +version = "1.63.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, - {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, + {file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"}, + {file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"}, ] [package.dependencies] @@ -954,104 +956,106 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [[package]] name = "grpcio" -version = "1.62.0" +version = "1.62.2" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-1.62.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:136ffd79791b1eddda8d827b607a6285474ff8a1a5735c4947b58c481e5e4271"}, - {file = "grpcio-1.62.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d6a56ba703be6b6267bf19423d888600c3f574ac7c2cc5e6220af90662a4d6b0"}, - {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4cd356211579043fce9f52acc861e519316fff93980a212c8109cca8f47366b6"}, - {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e803e9b58d8f9b4ff0ea991611a8d51b31c68d2e24572cd1fe85e99e8cc1b4f8"}, - {file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4c04fe33039b35b97c02d2901a164bbbb2f21fb9c4e2a45a959f0b044c3512c"}, - {file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:95370c71b8c9062f9ea033a0867c4c73d6f0ff35113ebd2618171ec1f1e903e0"}, - {file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c912688acc05e4ff012c8891803659d6a8a8b5106f0f66e0aed3fb7e77898fa6"}, - {file = "grpcio-1.62.0-cp310-cp310-win32.whl", hash = "sha256:821a44bd63d0f04e33cf4ddf33c14cae176346486b0df08b41a6132b976de5fc"}, - {file = "grpcio-1.62.0-cp310-cp310-win_amd64.whl", hash = "sha256:81531632f93fece32b2762247c4c169021177e58e725494f9a746ca62c83acaa"}, - {file = "grpcio-1.62.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3fa15850a6aba230eed06b236287c50d65a98f05054a0f01ccedf8e1cc89d57f"}, - {file = "grpcio-1.62.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:36df33080cd7897623feff57831eb83c98b84640b016ce443305977fac7566fb"}, - {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7a195531828b46ea9c4623c47e1dc45650fc7206f8a71825898dd4c9004b0928"}, - {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab140a3542bbcea37162bdfc12ce0d47a3cda3f2d91b752a124cc9fe6776a9e2"}, - {file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f9d6c3223914abb51ac564dc9c3782d23ca445d2864321b9059d62d47144021"}, - {file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fbe0c20ce9a1cff75cfb828b21f08d0a1ca527b67f2443174af6626798a754a4"}, - {file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38f69de9c28c1e7a8fd24e4af4264726637b72f27c2099eaea6e513e7142b47e"}, - {file = "grpcio-1.62.0-cp311-cp311-win32.whl", hash = "sha256:ce1aafdf8d3f58cb67664f42a617af0e34555fe955450d42c19e4a6ad41c84bd"}, - {file = "grpcio-1.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:eef1d16ac26c5325e7d39f5452ea98d6988c700c427c52cbc7ce3201e6d93334"}, - {file = "grpcio-1.62.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8aab8f90b2a41208c0a071ec39a6e5dbba16fd827455aaa070fec241624ccef8"}, - {file = "grpcio-1.62.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:62aa1659d8b6aad7329ede5d5b077e3d71bf488d85795db517118c390358d5f6"}, - {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0d7ae7fc7dbbf2d78d6323641ded767d9ec6d121aaf931ec4a5c50797b886532"}, - {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f359d635ee9428f0294bea062bb60c478a8ddc44b0b6f8e1f42997e5dc12e2ee"}, - {file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d48e5b1f8f4204889f1acf30bb57c30378e17c8d20df5acbe8029e985f735c"}, - {file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:662d3df5314ecde3184cf87ddd2c3a66095b3acbb2d57a8cada571747af03873"}, - {file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92cdb616be44c8ac23a57cce0243af0137a10aa82234f23cd46e69e115071388"}, - {file = "grpcio-1.62.0-cp312-cp312-win32.whl", hash = "sha256:0b9179478b09ee22f4a36b40ca87ad43376acdccc816ce7c2193a9061bf35701"}, - {file = "grpcio-1.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:614c3ed234208e76991992342bab725f379cc81c7dd5035ee1de2f7e3f7a9842"}, - {file = "grpcio-1.62.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:7e1f51e2a460b7394670fdb615e26d31d3260015154ea4f1501a45047abe06c9"}, - {file = "grpcio-1.62.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:bcff647e7fe25495e7719f779cc219bbb90b9e79fbd1ce5bda6aae2567f469f2"}, - {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:56ca7ba0b51ed0de1646f1735154143dcbdf9ec2dbe8cc6645def299bb527ca1"}, - {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e84bfb2a734e4a234b116be208d6f0214e68dcf7804306f97962f93c22a1839"}, - {file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c1488b31a521fbba50ae86423f5306668d6f3a46d124f7819c603979fc538c4"}, - {file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98d8f4eb91f1ce0735bf0b67c3b2a4fea68b52b2fd13dc4318583181f9219b4b"}, - {file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b3d3d755cfa331d6090e13aac276d4a3fb828bf935449dc16c3d554bf366136b"}, - {file = "grpcio-1.62.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a33f2bfd8a58a02aab93f94f6c61279be0f48f99fcca20ebaee67576cd57307b"}, - {file = "grpcio-1.62.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:5e709f7c8028ce0443bddc290fb9c967c1e0e9159ef7a030e8c21cac1feabd35"}, - {file = "grpcio-1.62.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:2f3d9a4d0abb57e5f49ed5039d3ed375826c2635751ab89dcc25932ff683bbb6"}, - {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:62ccb92f594d3d9fcd00064b149a0187c246b11e46ff1b7935191f169227f04c"}, - {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:921148f57c2e4b076af59a815467d399b7447f6e0ee10ef6d2601eb1e9c7f402"}, - {file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f897b16190b46bc4d4aaf0a32a4b819d559a37a756d7c6b571e9562c360eed72"}, - {file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1bc8449084fe395575ed24809752e1dc4592bb70900a03ca42bf236ed5bf008f"}, - {file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81d444e5e182be4c7856cd33a610154fe9ea1726bd071d07e7ba13fafd202e38"}, - {file = "grpcio-1.62.0-cp38-cp38-win32.whl", hash = "sha256:88f41f33da3840b4a9bbec68079096d4caf629e2c6ed3a72112159d570d98ebe"}, - {file = "grpcio-1.62.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc2836cb829895ee190813446dce63df67e6ed7b9bf76060262c55fcd097d270"}, - {file = "grpcio-1.62.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fcc98cff4084467839d0a20d16abc2a76005f3d1b38062464d088c07f500d170"}, - {file = "grpcio-1.62.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:0d3dee701e48ee76b7d6fbbba18ba8bc142e5b231ef7d3d97065204702224e0e"}, - {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b7a6be562dd18e5d5bec146ae9537f20ae1253beb971c0164f1e8a2f5a27e829"}, - {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29cb592c4ce64a023712875368bcae13938c7f03e99f080407e20ffe0a9aa33b"}, - {file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eda79574aec8ec4d00768dcb07daba60ed08ef32583b62b90bbf274b3c279f7"}, - {file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7eea57444a354ee217fda23f4b479a4cdfea35fb918ca0d8a0e73c271e52c09c"}, - {file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0e97f37a3b7c89f9125b92d22e9c8323f4e76e7993ba7049b9f4ccbe8bae958a"}, - {file = "grpcio-1.62.0-cp39-cp39-win32.whl", hash = "sha256:39cd45bd82a2e510e591ca2ddbe22352e8413378852ae814549c162cf3992a93"}, - {file = "grpcio-1.62.0-cp39-cp39-win_amd64.whl", hash = "sha256:b71c65427bf0ec6a8b48c68c17356cb9fbfc96b1130d20a07cb462f4e4dcdcd5"}, - {file = "grpcio-1.62.0.tar.gz", hash = "sha256:748496af9238ac78dcd98cce65421f1adce28c3979393e3609683fcd7f3880d7"}, + {file = "grpcio-1.62.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:66344ea741124c38588a664237ac2fa16dfd226964cca23ddc96bd4accccbde5"}, + {file = "grpcio-1.62.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:5dab7ac2c1e7cb6179c6bfad6b63174851102cbe0682294e6b1d6f0981ad7138"}, + {file = "grpcio-1.62.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:3ad00f3f0718894749d5a8bb0fa125a7980a2f49523731a9b1fabf2b3522aa43"}, + {file = "grpcio-1.62.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e72ddfee62430ea80133d2cbe788e0d06b12f865765cb24a40009668bd8ea05"}, + {file = "grpcio-1.62.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53d3a59a10af4c2558a8e563aed9f256259d2992ae0d3037817b2155f0341de1"}, + {file = "grpcio-1.62.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1511a303f8074f67af4119275b4f954189e8313541da7b88b1b3a71425cdb10"}, + {file = "grpcio-1.62.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b94d41b7412ef149743fbc3178e59d95228a7064c5ab4760ae82b562bdffb199"}, + {file = "grpcio-1.62.2-cp310-cp310-win32.whl", hash = "sha256:a75af2fc7cb1fe25785be7bed1ab18cef959a376cdae7c6870184307614caa3f"}, + {file = "grpcio-1.62.2-cp310-cp310-win_amd64.whl", hash = "sha256:80407bc007754f108dc2061e37480238b0dc1952c855e86a4fc283501ee6bb5d"}, + {file = "grpcio-1.62.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:c1624aa686d4b36790ed1c2e2306cc3498778dffaf7b8dd47066cf819028c3ad"}, + {file = "grpcio-1.62.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:1c1bb80299bdef33309dff03932264636450c8fdb142ea39f47e06a7153d3063"}, + {file = "grpcio-1.62.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:db068bbc9b1fa16479a82e1ecf172a93874540cb84be69f0b9cb9b7ac3c82670"}, + {file = "grpcio-1.62.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc8a308780edbe2c4913d6a49dbdb5befacdf72d489a368566be44cadaef1a"}, + {file = "grpcio-1.62.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0695ae31a89f1a8fc8256050329a91a9995b549a88619263a594ca31b76d756"}, + {file = "grpcio-1.62.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88b4f9ee77191dcdd8810241e89340a12cbe050be3e0d5f2f091c15571cd3930"}, + {file = "grpcio-1.62.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a0204532aa2f1afd467024b02b4069246320405bc18abec7babab03e2644e75"}, + {file = "grpcio-1.62.2-cp311-cp311-win32.whl", hash = "sha256:6e784f60e575a0de554ef9251cbc2ceb8790914fe324f11e28450047f264ee6f"}, + {file = "grpcio-1.62.2-cp311-cp311-win_amd64.whl", hash = "sha256:112eaa7865dd9e6d7c0556c8b04ae3c3a2dc35d62ad3373ab7f6a562d8199200"}, + {file = "grpcio-1.62.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:65034473fc09628a02fb85f26e73885cf1ed39ebd9cf270247b38689ff5942c5"}, + {file = "grpcio-1.62.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d2c1771d0ee3cf72d69bb5e82c6a82f27fbd504c8c782575eddb7839729fbaad"}, + {file = "grpcio-1.62.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:3abe6838196da518863b5d549938ce3159d809218936851b395b09cad9b5d64a"}, + {file = "grpcio-1.62.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5ffeb269f10cedb4f33142b89a061acda9f672fd1357331dbfd043422c94e9e"}, + {file = "grpcio-1.62.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404d3b4b6b142b99ba1cff0b2177d26b623101ea2ce51c25ef6e53d9d0d87bcc"}, + {file = "grpcio-1.62.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:262cda97efdabb20853d3b5a4c546a535347c14b64c017f628ca0cc7fa780cc6"}, + {file = "grpcio-1.62.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17708db5b11b966373e21519c4c73e5a750555f02fde82276ea2a267077c68ad"}, + {file = "grpcio-1.62.2-cp312-cp312-win32.whl", hash = "sha256:b7ec9e2f8ffc8436f6b642a10019fc513722858f295f7efc28de135d336ac189"}, + {file = "grpcio-1.62.2-cp312-cp312-win_amd64.whl", hash = "sha256:aa787b83a3cd5e482e5c79be030e2b4a122ecc6c5c6c4c42a023a2b581fdf17b"}, + {file = "grpcio-1.62.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:cfd23ad29bfa13fd4188433b0e250f84ec2c8ba66b14a9877e8bce05b524cf54"}, + {file = "grpcio-1.62.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:af15e9efa4d776dfcecd1d083f3ccfb04f876d613e90ef8432432efbeeac689d"}, + {file = "grpcio-1.62.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:f4aa94361bb5141a45ca9187464ae81a92a2a135ce2800b2203134f7a1a1d479"}, + {file = "grpcio-1.62.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82af3613a219512a28ee5c95578eb38d44dd03bca02fd918aa05603c41018051"}, + {file = "grpcio-1.62.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55ddaf53474e8caeb29eb03e3202f9d827ad3110475a21245f3c7712022882a9"}, + {file = "grpcio-1.62.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79b518c56dddeec79e5500a53d8a4db90da995dfe1738c3ac57fe46348be049"}, + {file = "grpcio-1.62.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5eb4844e5e60bf2c446ef38c5b40d7752c6effdee882f716eb57ae87255d20a"}, + {file = "grpcio-1.62.2-cp37-cp37m-win_amd64.whl", hash = "sha256:aaae70364a2d1fb238afd6cc9fcb10442b66e397fd559d3f0968d28cc3ac929c"}, + {file = "grpcio-1.62.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:1bcfe5070e4406f489e39325b76caeadab28c32bf9252d3ae960c79935a4cc36"}, + {file = "grpcio-1.62.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:da6a7b6b938c15fa0f0568e482efaae9c3af31963eec2da4ff13a6d8ec2888e4"}, + {file = "grpcio-1.62.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:41955b641c34db7d84db8d306937b72bc4968eef1c401bea73081a8d6c3d8033"}, + {file = "grpcio-1.62.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c772f225483905f675cb36a025969eef9712f4698364ecd3a63093760deea1bc"}, + {file = "grpcio-1.62.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07ce1f775d37ca18c7a141300e5b71539690efa1f51fe17f812ca85b5e73262f"}, + {file = "grpcio-1.62.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:26f415f40f4a93579fd648f48dca1c13dfacdfd0290f4a30f9b9aeb745026811"}, + {file = "grpcio-1.62.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:db707e3685ff16fc1eccad68527d072ac8bdd2e390f6daa97bc394ea7de4acea"}, + {file = "grpcio-1.62.2-cp38-cp38-win32.whl", hash = "sha256:589ea8e75de5fd6df387de53af6c9189c5231e212b9aa306b6b0d4f07520fbb9"}, + {file = "grpcio-1.62.2-cp38-cp38-win_amd64.whl", hash = "sha256:3c3ed41f4d7a3aabf0f01ecc70d6b5d00ce1800d4af652a549de3f7cf35c4abd"}, + {file = "grpcio-1.62.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:162ccf61499c893831b8437120600290a99c0bc1ce7b51f2c8d21ec87ff6af8b"}, + {file = "grpcio-1.62.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:f27246d7da7d7e3bd8612f63785a7b0c39a244cf14b8dd9dd2f2fab939f2d7f1"}, + {file = "grpcio-1.62.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:2507006c8a478f19e99b6fe36a2464696b89d40d88f34e4b709abe57e1337467"}, + {file = "grpcio-1.62.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a90ac47a8ce934e2c8d71e317d2f9e7e6aaceb2d199de940ce2c2eb611b8c0f4"}, + {file = "grpcio-1.62.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99701979bcaaa7de8d5f60476487c5df8f27483624f1f7e300ff4669ee44d1f2"}, + {file = "grpcio-1.62.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:af7dc3f7a44f10863b1b0ecab4078f0a00f561aae1edbd01fd03ad4dcf61c9e9"}, + {file = "grpcio-1.62.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fa63245271920786f4cb44dcada4983a3516be8f470924528cf658731864c14b"}, + {file = "grpcio-1.62.2-cp39-cp39-win32.whl", hash = "sha256:c6ad9c39704256ed91a1cffc1379d63f7d0278d6a0bad06b0330f5d30291e3a3"}, + {file = "grpcio-1.62.2-cp39-cp39-win_amd64.whl", hash = "sha256:16da954692fd61aa4941fbeda405a756cd96b97b5d95ca58a92547bba2c1624f"}, + {file = "grpcio-1.62.2.tar.gz", hash = "sha256:c77618071d96b7a8be2c10701a98537823b9c65ba256c0b9067e0594cdbd954d"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.62.0)"] +protobuf = ["grpcio-tools (>=1.62.2)"] [[package]] name = "grpcio-status" -version = "1.62.0" +version = "1.62.2" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.6" files = [ - {file = "grpcio-status-1.62.0.tar.gz", hash = "sha256:0d693e9c09880daeaac060d0c3dba1ae470a43c99e5d20dfeafd62cf7e08a85d"}, - {file = "grpcio_status-1.62.0-py3-none-any.whl", hash = "sha256:3baac03fcd737310e67758c4082a188107f771d32855bce203331cd4c9aa687a"}, + {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"}, + {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.0" +grpcio = ">=1.62.2" protobuf = ">=4.21.6" [[package]] name = "gunicorn" -version = "20.1.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, - {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] -setuptools = ">=3.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -1128,13 +1132,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1469,50 +1473,50 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.21.0" +version = "1.21.1" description = "NumPy is the fundamental package for array computing with Python." optional = false python-versions = ">=3.7" files = [ - {file = "numpy-1.21.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d5caa946a9f55511e76446e170bdad1d12d6b54e17a2afe7b189112ed4412bb8"}, - {file = "numpy-1.21.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ac4fd578322842dbda8d968e3962e9f22e862b6ec6e3378e7415625915e2da4d"}, - {file = "numpy-1.21.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:598fe100b2948465cf3ed64b1a326424b5e4be2670552066e17dfaa67246011d"}, - {file = "numpy-1.21.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c55407f739f0bfcec67d0df49103f9333edc870061358ac8a8c9e37ea02fcd2"}, - {file = "numpy-1.21.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:75579acbadbf74e3afd1153da6177f846212ea2a0cc77de53523ae02c9256513"}, - {file = "numpy-1.21.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc367c86eb87e5b7c9592935620f22d13b090c609f1b27e49600cd033b529f54"}, - {file = "numpy-1.21.0-cp37-cp37m-win32.whl", hash = "sha256:d89b0dc7f005090e32bb4f9bf796e1dcca6b52243caf1803fdd2b748d8561f63"}, - {file = "numpy-1.21.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eda2829af498946c59d8585a9fd74da3f810866e05f8df03a86f70079c7531dd"}, - {file = "numpy-1.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1a784e8ff7ea2a32e393cc53eb0003eca1597c7ca628227e34ce34eb11645a0e"}, - {file = "numpy-1.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bba474a87496d96e61461f7306fba2ebba127bed7836212c360f144d1e72ac54"}, - {file = "numpy-1.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd0a359c1c17f00cb37de2969984a74320970e0ceef4808c32e00773b06649d9"}, - {file = "numpy-1.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4d5a86a5257843a18fb1220c5f1c199532bc5d24e849ed4b0289fb59fbd4d8f"}, - {file = "numpy-1.21.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:620732f42259eb2c4642761bd324462a01cdd13dd111740ce3d344992dd8492f"}, - {file = "numpy-1.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9205711e5440954f861ceeea8f1b415d7dd15214add2e878b4d1cf2bcb1a914"}, - {file = "numpy-1.21.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ad09f55cc95ed8d80d8ab2052f78cc21cb231764de73e229140d81ff49d8145e"}, - {file = "numpy-1.21.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1f2fb2da242568af0271455b89aee0f71e4e032086ee2b4c5098945d0e11cf6"}, - {file = "numpy-1.21.0-cp38-cp38-win32.whl", hash = "sha256:e58ddb53a7b4959932f5582ac455ff90dcb05fac3f8dcc8079498d43afbbde6c"}, - {file = "numpy-1.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:d2910d0a075caed95de1a605df00ee03b599de5419d0b95d55342e9a33ad1fb3"}, - {file = "numpy-1.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a290989cd671cd0605e9c91a70e6df660f73ae87484218e8285c6522d29f6e38"}, - {file = "numpy-1.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3537b967b350ad17633b35c2f4b1a1bbd258c018910b518c30b48c8e41272717"}, - {file = "numpy-1.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc6c650f8700ce1e3a77668bb7c43e45c20ac06ae00d22bdf6760b38958c883"}, - {file = "numpy-1.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:709884863def34d72b183d074d8ba5cfe042bc3ff8898f1ffad0209161caaa99"}, - {file = "numpy-1.21.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bebab3eaf0641bba26039fb0b2c5bf9b99407924b53b1ea86e03c32c64ef5aef"}, - {file = "numpy-1.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf680682ad0a3bef56dae200dbcbac2d57294a73e5b0f9864955e7dd7c2c2491"}, - {file = "numpy-1.21.0-cp39-cp39-win32.whl", hash = "sha256:d95d16204cd51ff1a1c8d5f9958ce90ae190be81d348b514f9be39f878b8044a"}, - {file = "numpy-1.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:2ba579dde0563f47021dcd652253103d6fd66165b18011dce1a0609215b2791e"}, - {file = "numpy-1.21.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c40e6b860220ed862e8097b8f81c9af6d7405b723f4a7af24a267b46f90e461"}, - {file = "numpy-1.21.0.zip", hash = "sha256:e80fe25cba41c124d04c662f33f6364909b985f2eb5998aaa5ae4b9587242cce"}, + {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, + {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, + {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, + {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, + {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, + {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, + {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, + {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, + {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, ] [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -1551,10 +1555,10 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, {version = ">=1.17.3", markers = "(platform_machine != \"aarch64\" and platform_machine != \"arm64\") and python_version < \"3.10\""}, {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, ] python-dateutil = ">=2.7.3" pytz = ">=2017.3" @@ -1682,7 +1686,7 @@ files = [ name = "pyarrow" version = "12.0.1" description = "Python library for Apache Arrow" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "pyarrow-12.0.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6d288029a94a9bb5407ceebdd7110ba398a00412c5b0155ee9813a40d246c5df"}, @@ -2637,13 +2641,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.25.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, + {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, ] [package.dependencies] @@ -2653,7 +2657,7 @@ importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -2700,9 +2704,10 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] +bigquery = ["db-dtypes", "google-cloud-bigquery"] hdf5 = ["h5py"] [metadata] lock-version = "2.0" python-versions = "^3.7.1" -content-hash = "1572a843b2a77b5376349ff6cd212d39644931ab91b8e7b61c12709eda55617e" +content-hash = "ae8933728fc5d778c4437e1869d340f8adab557187b500bb99e6d1f23099cb44" diff --git a/pyproject.toml b/pyproject.toml index 1d24ee7d1..e122881e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "octue" -version = "0.53.0" +version = "0.54.0" description = "A package providing template applications for data services, and a python SDK to the Octue API." readme = "README.md" authors = ["Marcus Lugg ", "Thomas Clark "] @@ -29,17 +29,18 @@ google-cloud-pubsub = "^2.5" google-cloud-secret-manager = "^2.3" google-cloud-storage = ">=1.35.1, <3" google-crc32c = "^1.1" -gunicorn = "^20.1" +gunicorn = "^22" python-dateutil = "^2.8" pyyaml = "^6" h5py = { version = "^3.6", optional = true } twined = "^0.5.1" packaging = ">=20.4" -google-cloud-bigquery = "^3.18.0" -db-dtypes = "^1.2.0" +google-cloud-bigquery = { version = "^3.18.0", optional = true } +db-dtypes = { version = "^1.2.0", optional = true } [tool.poetry.extras] hdf5 = ["h5py"] +bigquery = ["google-cloud-bigquery", "db-dtypes"] [tool.poetry.scripts] octue = "octue.cli:octue_cli" @@ -55,7 +56,7 @@ black = "22.6.0" pre-commit = "^2.17" coverage = "^5" # Template app dependencies -numpy = "1.21.0" +numpy = "^1" dateparser = "1.1.1" stringcase = "1.2.0" pandas = "^1.3" diff --git a/terraform/bigquery.tf b/terraform/bigquery.tf index 63abffe09..4c0034b89 100644 --- a/terraform/bigquery.tf +++ b/terraform/bigquery.tf @@ -25,6 +25,11 @@ resource "google_bigquery_table" "test_table" { "type": "STRING", "mode": "REQUIRED" }, + { + "name": "kind", + "type": "STRING", + "mode": "REQUIRED" + }, { "name": "event", "type": "JSON", diff --git a/terraform/functions.tf b/terraform/functions.tf index c0be67fe8..250ca2882 100644 --- a/terraform/functions.tf +++ b/terraform/functions.tf @@ -9,7 +9,7 @@ resource "google_cloudfunctions2_function" "event_handler" { source { storage_source { bucket = "twined-gcp" - object = "event_handler/0.4.0.zip" + object = "event_handler/0.5.0.zip" } } } diff --git a/terraform/main.tf b/terraform/main.tf index 89e7e5561..e858236dd 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -24,6 +24,7 @@ resource "google_project_service" "pub_sub" { } } + resource "google_project_service" "cloud_resource_manager" { project = var.project service = "cloudresourcemanager.googleapis.com" @@ -68,9 +69,32 @@ resource "google_project_service" "cloud_run" { } +resource "google_project_service" "cloud_functions" { + project = var.project + service = "cloudfunctions.googleapis.com" +} + + +resource "google_project_service" "eventarc" { + project = var.project + service = "eventarc.googleapis.com" +} + + +resource "google_project_service" "cloud_build" { + project = var.project + service = "cloudbuild.googleapis.com" +} + + +resource "google_project_service" "bigquery" { + project = var.project + service = "bigquery.googleapis.com" +} + + provider "google" { credentials = file(var.credentials_file) project = var.project region = var.region -# zone = var.zone } diff --git a/tests/cloud/deployment/google/cloud_run/test_cloud_run_deployment.py b/tests/cloud/deployment/google/cloud_run/test_cloud_run_deployment.py index bb6e12903..76f60e304 100644 --- a/tests/cloud/deployment/google/cloud_run/test_cloud_run_deployment.py +++ b/tests/cloud/deployment/google/cloud_run/test_cloud_run_deployment.py @@ -1,11 +1,16 @@ import os +import time import unittest from unittest import TestCase import twined.exceptions +from octue.cloud.pub_sub.bigquery import get_events from octue.resources import Child +EXAMPLE_SERVICE_SRUID = "octue/example-service-cloud-run:0.4.2" + + @unittest.skipUnless( condition=os.getenv("RUN_CLOUD_RUN_DEPLOYMENT_TEST", "0").lower() == "1", reason="'RUN_CLOUD_RUN_DEPLOYMENT_TEST' environment variable is False or not present.", @@ -13,7 +18,7 @@ class TestCloudRunDeployment(TestCase): # This is the service ID of the example service deployed to Google Cloud Run. child = Child( - id="octue/example-service-cloud-run:0.4.0", + id=EXAMPLE_SERVICE_SRUID, backend={"name": "GCPPubSubBackend", "project_name": os.environ["TEST_PROJECT_NAME"]}, ) @@ -26,7 +31,7 @@ def test_synchronous_question(self): """Test that the Google Cloud Run example deployment works, providing a service that can be asked questions and send responses. """ - answer = self.child.ask(input_values={"n_iterations": 3}) + answer, _ = self.child.ask(input_values={"n_iterations": 3}) # Check the output values. self.assertEqual(answer["output_values"], [1, 2, 3, 4, 5]) @@ -36,6 +41,23 @@ def test_synchronous_question(self): self.assertEqual(f.read(), "This is some example service output.") def test_asynchronous_question(self): - """Test asking an asynchronous question.""" - answer = self.child.ask(input_values={"n_iterations": 3}, asynchronous=True) + """Test asking an asynchronous question and retrieving the resulting events from the event store.""" + answer, question_uuid = self.child.ask(input_values={"n_iterations": 3}, asynchronous=True) self.assertIsNone(answer) + + # Wait for question to complete. + time.sleep(10) + + events = get_events( + table_id="octue_sdk_python_test_dataset.service-events", + sender=EXAMPLE_SERVICE_SRUID, + question_uuid=question_uuid, + kind="result", + ) + + # Check the output values. + self.assertEqual(events[0]["event"]["output_values"], [1, 2, 3, 4, 5]) + + # Check that the output dataset and its files can be accessed. + with events[0]["event"]["output_manifest"].datasets["example_dataset"].files.one() as (datafile, f): + self.assertEqual(f.read(), "This is some example service output.") diff --git a/tests/cloud/emulators/test_child_emulator.py b/tests/cloud/emulators/test_child_emulator.py index 755058f95..22e334176 100644 --- a/tests/cloud/emulators/test_child_emulator.py +++ b/tests/cloud/emulators/test_child_emulator.py @@ -84,7 +84,7 @@ def test_ask_with_result_event(self): child_emulator = ChildEmulator(backend=self.BACKEND, events=events) - result = child_emulator.ask(input_values={"hello": "world"}) + result, _ = child_emulator.ask(input_values={"hello": "world"}) self.assertEqual(result["output_values"], [1, 2, 3, 4]) self.assertEqual(result["output_manifest"].id, output_manifest.id) @@ -102,13 +102,13 @@ def test_ask_with_input_manifest(self): child_emulator = ChildEmulator(backend=self.BACKEND, events=events) - result = child_emulator.ask(input_values={"hello": "world"}, input_manifest=input_manifest) + result, _ = child_emulator.ask(input_values={"hello": "world"}, input_manifest=input_manifest) self.assertEqual(result["output_values"], [1, 2, 3, 4]) def test_empty_output_returned_by_ask_if_no_result_present_in_events(self): """Test that an empty output is returned if no result event is present in the given events.""" child_emulator = ChildEmulator(backend=self.BACKEND, events=[]) - result = child_emulator.ask(input_values={"hello": "world"}) + result, _ = child_emulator.ask(input_values={"hello": "world"}) self.assertEqual(result, {"output_values": None, "output_manifest": None}) def test_ask_with_log_record_with_missing_log_record_key(self): @@ -288,8 +288,8 @@ def test_ask_more_than_one_question(self): ] child_emulator = ChildEmulator(backend=self.BACKEND, events=events) - result_0 = child_emulator.ask(input_values={"hello": "world"}) - result_1 = child_emulator.ask(input_values={"hello": "planet"}) + result_0, _ = child_emulator.ask(input_values={"hello": "world"}) + result_1, _ = child_emulator.ask(input_values={"hello": "planet"}) self.assertEqual(result_0, result_1) @@ -302,7 +302,7 @@ def test_with_empty_file(self): object in), asked a question, and produce a trivial result. """ child_emulator = ChildEmulator.from_file(os.path.join(self.TEST_FILES_DIRECTORY, "empty_file.json")) - result = child_emulator.ask(input_values={"hello": "world"}) + result, _ = child_emulator.ask(input_values={"hello": "world"}) self.assertEqual(result, {"output_values": None, "output_manifest": None}) def test_with_only_events(self): @@ -316,7 +316,7 @@ def test_with_only_events(self): monitor_messages = [] with self.assertLogs(level=logging.INFO) as logging_context: - result = child_emulator.ask( + result, _ = child_emulator.ask( input_values={"hello": "world"}, handle_monitor_message=lambda value: monitor_messages.append(value), ) @@ -343,7 +343,7 @@ def test_with_full_file(self): monitor_messages = [] with self.assertLogs(level=logging.INFO) as logging_context: - result = child_emulator.ask( + result, _ = child_emulator.ask( input_values={"hello": "world"}, handle_monitor_message=lambda value: monitor_messages.append(value), ) @@ -399,7 +399,7 @@ def test_with_output_manifest(self): }, ) - result = child_emulator.ask(input_values={"hello": "world"}) + result, _ = child_emulator.ask(input_values={"hello": "world"}) # Check that the output manifest has been produced correctly. self.assertEqual(result["output_manifest"].id, expected_output_manifest.id) diff --git a/tests/cloud/pub_sub/test_bigquery.py b/tests/cloud/pub_sub/test_bigquery.py index 0209e339a..0057dd420 100644 --- a/tests/cloud/pub_sub/test_bigquery.py +++ b/tests/cloud/pub_sub/test_bigquery.py @@ -1,7 +1,22 @@ from unittest import TestCase -from unittest.mock import patch +from unittest.mock import MagicMock, patch from octue.cloud.pub_sub.bigquery import get_events +from octue.exceptions import ServiceNotFound + + +class MockEmptyResult: + """A mock empty query result.""" + + def result(self): + return MagicMock(total_rows=0) + + +class MockEmptyBigQueryClient: + """A mock BigQuery client that returns a mock empty query result.""" + + def query(self, *args, **kwargs): + return MockEmptyResult() class TestGetEvents(TestCase): @@ -15,6 +30,12 @@ def test_error_raised_if_event_kind_invalid(self): kind="frisbee_tournament", ) + def test_error_raised_if_no_events_found(self): + """Test that an error is raised if no events are found.""" + with patch("octue.cloud.pub_sub.bigquery.Client", MockEmptyBigQueryClient): + with self.assertRaises(ServiceNotFound): + get_events(table_id="blah", sender="octue/test-service:1.0.0", question_uuid="blah") + def test_without_kind(self): """Test the query used to retrieve events of all kinds.""" with patch("octue.cloud.pub_sub.bigquery.Client") as mock_client: @@ -22,8 +43,9 @@ def test_without_kind(self): self.assertEqual( mock_client.mock_calls[1].args[0], - "SELECT `event` FROM `blah`\nWHERE sender=@sender\nAND question_uuid=@question_uuid\n" - "ORDER BY `order`\nLIMIT @limit", + "SELECT `event`, `kind`, `datetime`, `uuid`, `originator`, `sender`, `sender_type`, `sender_sdk_version`, " + "`recipient`, `order`, `other_attributes` FROM `blah`\nWHERE sender=@sender\n" + "AND question_uuid=@question_uuid\nORDER BY `order`\nLIMIT @limit", ) def test_with_kind(self): @@ -33,24 +55,9 @@ def test_with_kind(self): self.assertEqual( mock_client.mock_calls[1].args[0], - "SELECT `event` FROM `blah`\nWHERE sender=@sender\nAND question_uuid=@question_uuid\n" - 'AND JSON_EXTRACT_SCALAR(event, "$.kind") = "result"\nORDER BY `order`\nLIMIT @limit', - ) - - def test_with_attributes(self): - """Test the query used to retrieve attributes in addition to events.""" - with patch("octue.cloud.pub_sub.bigquery.Client") as mock_client: - get_events( - table_id="blah", - sender="octue/test-service:1.0.0", - question_uuid="blah", - include_attributes=True, - ) - - self.assertEqual( - mock_client.mock_calls[1].args[0], - "SELECT `event`, `datetime`, `uuid`, `originator`, `sender`, `sender_type`, `sender_sdk_version`, `recipient`, `order`, `other_attributes` FROM `blah`\n" - "WHERE sender=@sender\nAND question_uuid=@question_uuid\nORDER BY `order`\nLIMIT @limit", + "SELECT `event`, `kind`, `datetime`, `uuid`, `originator`, `sender`, `sender_type`, `sender_sdk_version`, " + "`recipient`, `order`, `other_attributes` FROM `blah`\nWHERE sender=@sender\n" + "AND question_uuid=@question_uuid\nAND kind='result'\nORDER BY `order`\nLIMIT @limit", ) def test_with_backend_metadata(self): @@ -65,6 +72,7 @@ def test_with_backend_metadata(self): self.assertEqual( mock_client.mock_calls[1].args[0], - "SELECT `event`, `backend`, `backend_metadata` FROM `blah`\n" + "SELECT `event`, `kind`, `datetime`, `uuid`, `originator`, `sender`, `sender_type`, `sender_sdk_version`, " + "`recipient`, `order`, `other_attributes`, `backend`, `backend_metadata` FROM `blah`\n" "WHERE sender=@sender\nAND question_uuid=@question_uuid\nORDER BY `order`\nLIMIT @limit", ) diff --git a/tests/cloud/pub_sub/test_service.py b/tests/cloud/pub_sub/test_service.py index 8e6526acb..6900565a9 100644 --- a/tests/cloud/pub_sub/test_service.py +++ b/tests/cloud/pub_sub/test_service.py @@ -801,7 +801,9 @@ def test_providing_dynamic_children(self): def mock_child_app(analysis): analysis.children["expected_child"]._service = child - analysis.output_values = analysis.children["expected_child"].ask(input_values=[1, 2, 3, 4])["output_values"] + analysis.output_values = analysis.children["expected_child"].ask(input_values=[1, 2, 3, 4])[0][ + "output_values" + ] static_children = [ { diff --git a/tests/resources/test_child.py b/tests/resources/test_child.py index c0e866736..4acbea5ef 100644 --- a/tests/resources/test_child.py +++ b/tests/resources/test_child.py @@ -96,8 +96,8 @@ def mock_run_function(analysis_id, input_values, *args, **kwargs): # Make sure the child's underlying mock service knows how to access the mock responding service. child._service.children[responding_service.id] = responding_service - self.assertEqual(child.ask([1, 2, 3, 4])["output_values"], [1, 2, 3, 4]) - self.assertEqual(child.ask([5, 6, 7, 8])["output_values"], [5, 6, 7, 8]) + self.assertEqual(child.ask([1, 2, 3, 4])[0]["output_values"], [1, 2, 3, 4]) + self.assertEqual(child.ask([5, 6, 7, 8])[0]["output_values"], [5, 6, 7, 8]) def test_ask_multiple(self): """Test that a child can be asked multiple questions in parallel and return the answers in the correct order.""" @@ -122,7 +122,7 @@ def mock_run_function(analysis_id, input_values, *args, **kwargs): ) self.assertEqual( - answers, + [answer[0] for answer in answers], [ {"output_values": [1, 2, 3, 4], "output_manifest": None}, {"output_values": [5, 6, 7, 8], "output_manifest": None}, @@ -231,7 +231,7 @@ def test_ask_multiple_with_failed_question_retry(self): # Check that both questions succeeded. self.assertEqual( - answers, + [answer[0] for answer in answers], [ {"output_manifest": None, "output_values": [1, 2, 3, 4]}, {"output_manifest": None, "output_values": [5, 6, 7, 8]}, @@ -275,7 +275,7 @@ def test_ask_multiple_with_multiple_failed_question_retries(self): # Check that all four questions succeeded. self.assertEqual( - answers, + [answer[0] for answer in answers], [ {"output_manifest": None, "output_values": [1, 2, 3, 4]}, {"output_manifest": None, "output_values": [5, 6, 7, 8]}, diff --git a/tests/test_cli.py b/tests/test_cli.py index 153e2eff0..97c02da0c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -231,7 +231,7 @@ def test_start_command_with_revision_tag_override_when_revision_tag_environment_ ] ): with ServicePatcher(): - with self.assertLogs(level=logging.WARNING) as logging_context: + with self.assertLogs() as logging_context: result = CliRunner().invoke(octue_cli, ["start", "--revision-tag=hello", "--timeout=0"]) self.assertEqual(