From 6622d350895a3f62742ed5662bb4234d0d32b085 Mon Sep 17 00:00:00 2001 From: Will Miller Date: Wed, 11 Sep 2024 12:20:09 +0100 Subject: [PATCH] feat(clients): allow non-dev API endpoint roots Specifying an API endpoint root to a client used to imply that the endpoint in use was for dev, which would disable TLS and token bearer authorisation. This is not always a valid assumption, for example when manually specifying a locational endpoint for Google PubSub to target a specific region, as such endpoints are for production and should therefore use TLS and authorisation. Fix this by allowing manual configuration of the `api_is_dev` setting when using a non-dev root, whilst maintaining the old behaviour by default for backwards compatibility. --- auth/poetry.rest.lock | 22 +++---- auth/pyproject.rest.toml | 6 +- bigquery/gcloud/aio/bigquery/bigquery.py | 13 ++-- bigquery/poetry.rest.lock | 26 ++++---- bigquery/pyproject.rest.toml | 4 +- bigquery/tests/unit/bigquery_test.py | 42 +++++++++++++ datastore/gcloud/aio/datastore/datastore.py | 12 +++- datastore/poetry.rest.lock | 26 ++++---- datastore/pyproject.rest.toml | 4 +- datastore/tests/unit/datastore_test.py | 40 ++++++++++++ kms/gcloud/aio/kms/kms.py | 12 +++- kms/poetry.lock | 22 ++++++- kms/poetry.rest.lock | 24 +++---- kms/pyproject.rest.toml | 4 +- kms/pyproject.toml | 2 + kms/tests/unit/kms_test.py | 47 +++++++++++++- pubsub/gcloud/aio/pubsub/publisher_client.py | 13 ++-- pubsub/gcloud/aio/pubsub/subscriber_client.py | 13 ++-- pubsub/poetry.rest.lock | 24 +++---- pubsub/pyproject.rest.toml | 4 +- pubsub/tests/unit/publisher_test.py | 63 +++++++++++++++++++ pubsub/tests/unit/subscriber_test.py | 63 ++++++++++++++++++- storage/gcloud/aio/storage/storage.py | 13 ++-- storage/poetry.rest.lock | 29 +++++---- storage/pyproject.rest.toml | 8 +-- storage/tests/unit/storage_test.py | 60 +++++++++++++++++- taskqueue/gcloud/aio/taskqueue/queue.py | 13 ++-- taskqueue/poetry.rest.lock | 24 +++---- taskqueue/pyproject.rest.toml | 4 +- taskqueue/tests/unit/queue_test.py | 47 +++++++++++++- 30 files changed, 547 insertions(+), 137 deletions(-) create mode 100644 pubsub/tests/unit/publisher_test.py diff --git a/auth/poetry.rest.lock b/auth/poetry.rest.lock index 5d8555ad1..512a85024 100644 --- a/auth/poetry.rest.lock +++ b/auth/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -353,13 +353,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -367,7 +367,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -424,13 +424,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -442,4 +442,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "0a8e3c7f35afc47adf19cfc80379283f411511be97c5400c593f1e9d94ec1334" +content-hash = "33d10c6c4fa764ec12e79a7cb93f22b67a92e4e174d364b1b47ad1bb411518ca" diff --git a/auth/pyproject.rest.toml b/auth/pyproject.rest.toml index 82b3b6561..cb83cc684 100644 --- a/auth/pyproject.rest.toml +++ b/auth/pyproject.rest.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" readme = "README.rst" @@ -30,8 +30,8 @@ pyjwt = ">= 1.5.3, < 3.0.0" requests = ">= 2.2.1, < 3.0.0" [tool.poetry.group.dev.dependencies] -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/bigquery/gcloud/aio/bigquery/bigquery.py b/bigquery/gcloud/aio/bigquery/bigquery.py index 3473719c9..7828fffcc 100644 --- a/bigquery/gcloud/aio/bigquery/bigquery.py +++ b/bigquery/gcloud/aio/bigquery/bigquery.py @@ -29,9 +29,14 @@ log = logging.getLogger(__name__) -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('BIGQUERY_EMULATOR_HOST') if host: @@ -69,9 +74,9 @@ def __init__( self, project: Optional[str] = None, service_file: Optional[Union[str, IO[AnyStr]]] = None, session: Optional[Session] = None, token: Optional[Token] = None, - api_root: Optional[str] = None, + api_root: Optional[str] = None, api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self.session = AioSession(session) self.token = token or Token( service_file=service_file, scopes=SCOPES, diff --git a/bigquery/poetry.rest.lock b/bigquery/poetry.rest.lock index 3cd540dd7..f2aa88f70 100644 --- a/bigquery/poetry.rest.lock +++ b/bigquery/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -322,7 +322,7 @@ develop = false [package.dependencies] gcloud-rest-auth = ">= 5.3.0, < 6.0.0" -pyasn1-modules = ">= 0.2.1, < 0.4.1" +pyasn1-modules = ">= 0.2.1, < 0.4.2" rsa = ">= 3.1.4, < 5.0.0" [package.source] @@ -432,13 +432,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -446,7 +446,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -517,13 +517,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -535,4 +535,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "2b4c5e685ccbd1981c6304fba0d224993f96fd4e3e0cdc86327c06585f0edaa5" +content-hash = "9dd0c655978fdba6dd4fa3f494140b57745f3af95747b85e0128720734150d49" diff --git a/bigquery/pyproject.rest.toml b/bigquery/pyproject.rest.toml index 3b8765c36..989d2b751 100644 --- a/bigquery/pyproject.rest.toml +++ b/bigquery/pyproject.rest.toml @@ -27,8 +27,8 @@ gcloud-rest-auth = ">= 3.1.0, < 6.0.0" gcloud-rest-auth = { path = "../auth" } gcloud-rest-datastore = { path = "../datastore" } gcloud-rest-storage = { path = "../storage" } -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/bigquery/tests/unit/bigquery_test.py b/bigquery/tests/unit/bigquery_test.py index 06723732e..ed1409714 100644 --- a/bigquery/tests/unit/bigquery_test.py +++ b/bigquery/tests/unit/bigquery_test.py @@ -1,4 +1,8 @@ +from typing import Optional + +import pytest from gcloud.aio.bigquery import Table +from gcloud.aio.bigquery.bigquery import BigqueryBase def test_make_insert_body(): @@ -52,3 +56,41 @@ def test_make_insert_body_defult_id_fn(): assert len(body['rows']) == 2 assert all(r['insertId'] for r in body['rows']) + + +# ========= +# client +# ========= + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + async with BigqueryBase(token=_MockToken()) as bigquery: + assert 'Authorization' in await bigquery.headers() + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + async with BigqueryBase(api_root=api_root, token=_MockToken()) as bigquery: + assert 'Authorization' not in await bigquery.headers() + # If API specified to be dev, auth header should not be set + async with BigqueryBase( + api_root=api_root, api_is_dev=True, token=_MockToken(), + ) as bigquery: + assert 'Authorization' not in await bigquery.headers() + # If API specified to not be dev, auth header should be set + async with BigqueryBase( + api_root=api_root, api_is_dev=False, token=_MockToken(), + ) as bigquery: + assert 'Authorization' in await bigquery.headers() diff --git a/datastore/gcloud/aio/datastore/datastore.py b/datastore/gcloud/aio/datastore/datastore.py index e1785756c..45e38ddb9 100644 --- a/datastore/gcloud/aio/datastore/datastore.py +++ b/datastore/gcloud/aio/datastore/datastore.py @@ -44,9 +44,14 @@ LookUpResult = Dict[str, Union[str, List[Union[EntityResult, Key]]]] -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('DATASTORE_EMULATOR_HOST') if host: @@ -72,8 +77,9 @@ def __init__( service_file: Optional[Union[str, IO[AnyStr]]] = None, namespace: str = '', session: Optional[Session] = None, token: Optional[Token] = None, api_root: Optional[str] = None, + api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self.namespace = namespace self.session = AioSession(session) self.token = token or Token( diff --git a/datastore/poetry.rest.lock b/datastore/poetry.rest.lock index 16b3d6ff6..ae448e8cc 100644 --- a/datastore/poetry.rest.lock +++ b/datastore/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -306,7 +306,7 @@ develop = false [package.dependencies] gcloud-rest-auth = ">= 5.3.0, < 6.0.0" -pyasn1-modules = ">= 0.2.1, < 0.4.1" +pyasn1-modules = ">= 0.2.1, < 0.4.2" rsa = ">= 3.1.4, < 5.0.0" [package.source] @@ -416,13 +416,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -430,7 +430,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -501,13 +501,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -519,4 +519,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "2fbf77bdf723122dd0065a96d7a0e62515a7fba608415504d434f0096d3aa081" +content-hash = "09644a57b223ca0ce1aad4d82ef50b4544c5424031798809458ae5866f9edd92" diff --git a/datastore/pyproject.rest.toml b/datastore/pyproject.rest.toml index 1dcabced4..976bf1121 100644 --- a/datastore/pyproject.rest.toml +++ b/datastore/pyproject.rest.toml @@ -27,8 +27,8 @@ gcloud-rest-auth = ">= 3.1.0, < 6.0.0" # aiohttp = "3.9.1" gcloud-rest-auth = { path = "../auth" } gcloud-rest-storage = { path = "../storage" } -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/datastore/tests/unit/datastore_test.py b/datastore/tests/unit/datastore_test.py index 42809dcbd..38ac29c89 100644 --- a/datastore/tests/unit/datastore_test.py +++ b/datastore/tests/unit/datastore_test.py @@ -1,3 +1,5 @@ +from typing import Optional + import pytest from gcloud.aio.datastore import Datastore from gcloud.aio.datastore import Key @@ -21,3 +23,41 @@ def test_make_mutation_from_value_object(key): def key() -> Key: path = PathElement(kind='my-kind', name='path-name') return Key(project='my-project', path=[path], namespace='my-namespace') + +# ========= +# client +# ========= + + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + async with Datastore(token=_MockToken()) as datastore: + assert 'Authorization' in await datastore.headers() + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + async with Datastore(api_root=api_root, token=_MockToken()) as datastore: + assert 'Authorization' not in await datastore.headers() + # If API specified to be dev, auth header should not be set + async with Datastore( + api_root=api_root, api_is_dev=True, token=_MockToken(), + ) as datastore: + assert 'Authorization' not in await datastore.headers() + # If API specified to not be dev, auth header should be set + async with Datastore( + api_root=api_root, api_is_dev=False, token=_MockToken(), + ) as datastore: + assert 'Authorization' in await datastore.headers() diff --git a/kms/gcloud/aio/kms/kms.py b/kms/gcloud/aio/kms/kms.py index 0cfca9aa9..dd37ce080 100644 --- a/kms/gcloud/aio/kms/kms.py +++ b/kms/gcloud/aio/kms/kms.py @@ -27,9 +27,14 @@ ] -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('KMS_EMULATOR_HOST') if host: @@ -47,8 +52,9 @@ def __init__( service_file: Optional[Union[str, IO[AnyStr]]] = None, location: str = 'global', session: Optional[Session] = None, token: Optional[Token] = None, api_root: Optional[str] = None, + api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self._api_root = ( f'{self._api_root}/projects/{keyproject}/locations/{location}/' f'keyRings/{keyring}/cryptoKeys/{keyname}' diff --git a/kms/poetry.lock b/kms/poetry.lock index 32a186b03..4b77b2857 100644 --- a/kms/poetry.lock +++ b/kms/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" @@ -608,6 +608,24 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "tomli" version = "2.0.1" @@ -725,4 +743,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "d44bdacbd2c02db9229cf30a4bd876486b3fa6d02506cd71f675c4357d4dbd31" +content-hash = "baaa434da3925859dc6751d0dac5aa8bde8e825ba1ee87bfa58ab211827ed810" diff --git a/kms/poetry.rest.lock b/kms/poetry.rest.lock index 68084df5e..e3f8fe0be 100644 --- a/kms/poetry.rest.lock +++ b/kms/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -373,13 +373,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -387,7 +387,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -427,13 +427,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -445,4 +445,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "eba025513927da74175a40cd478ca8d8581b557f442c95ffee54fa3a532c6883" +content-hash = "1b4716a9bba554a2e65d92707a34bf45ceb011b62ca2e5c4f6404d1b24aed665" diff --git a/kms/pyproject.rest.toml b/kms/pyproject.rest.toml index 71ef05259..0506c8059 100644 --- a/kms/pyproject.rest.toml +++ b/kms/pyproject.rest.toml @@ -25,7 +25,8 @@ gcloud-rest-auth = ">= 3.1.0, < 6.0.0" [tool.poetry.group.dev.dependencies] gcloud-rest-auth = { path = "../auth" } -pytest = "8.2.2" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" [[tool.poetry.source]] name = "pypi" @@ -33,6 +34,7 @@ priority = "primary" [tool.pytest.ini_options] addopts = "-Werror" +# asyncio_mode = "auto" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/kms/pyproject.toml b/kms/pyproject.toml index 1e906c794..0603a2186 100644 --- a/kms/pyproject.toml +++ b/kms/pyproject.toml @@ -26,6 +26,7 @@ gcloud-aio-auth = ">= 3.1.0, < 6.0.0" [tool.poetry.group.dev.dependencies] gcloud-aio-auth = { path = "../auth" } pytest = "8.3.3" +pytest-asyncio = "0.23.8" [[tool.poetry.source]] name = "pypi" @@ -33,6 +34,7 @@ priority = "primary" [tool.pytest.ini_options] addopts = "-Werror" +asyncio_mode = "auto" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/kms/tests/unit/kms_test.py b/kms/tests/unit/kms_test.py index c9ba54d1b..01e694c5f 100644 --- a/kms/tests/unit/kms_test.py +++ b/kms/tests/unit/kms_test.py @@ -1,5 +1,50 @@ -from gcloud.aio.kms import kms # pylint: disable=unused-import +from typing import Optional + +import pytest +from gcloud.aio.kms.kms import KMS def test_importable(): assert True + +# ========= +# client +# ========= + + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + async with KMS('foo', 'bar', 'baz', token=_MockToken()) as kms: + assert 'Authorization' in await kms.headers() + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + async with KMS( + 'foo', 'bar', 'baz', api_root=api_root, token=_MockToken(), + ) as kms: + assert 'Authorization' not in await kms.headers() + # If API specified to be dev, auth header should not be set + async with KMS( + 'foo', 'bar', 'baz', api_root=api_root, api_is_dev=True, + token=_MockToken(), + ) as kms: + assert 'Authorization' not in await kms.headers() + # If API specified to not be dev, auth header should be set + async with KMS( + 'foo', 'bar', 'baz', api_root=api_root, api_is_dev=False, + token=_MockToken(), + ) as kms: + assert 'Authorization' in await kms.headers() diff --git a/pubsub/gcloud/aio/pubsub/publisher_client.py b/pubsub/gcloud/aio/pubsub/publisher_client.py index 0170cc87e..43aacc0f1 100644 --- a/pubsub/gcloud/aio/pubsub/publisher_client.py +++ b/pubsub/gcloud/aio/pubsub/publisher_client.py @@ -30,9 +30,14 @@ log = logging.getLogger(__name__) -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('PUBSUB_EMULATOR_HOST') if host: @@ -49,9 +54,9 @@ class PublisherClient: def __init__( self, *, service_file: Optional[Union[str, IO[AnyStr]]] = None, session: Optional[Session] = None, token: Optional[Token] = None, - api_root: Optional[str] = None, + api_root: Optional[str] = None, api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self.session = AioSession(session, verify_ssl=not self._api_is_dev) self.token = token or Token( diff --git a/pubsub/gcloud/aio/pubsub/subscriber_client.py b/pubsub/gcloud/aio/pubsub/subscriber_client.py index 047414039..429137594 100644 --- a/pubsub/gcloud/aio/pubsub/subscriber_client.py +++ b/pubsub/gcloud/aio/pubsub/subscriber_client.py @@ -26,9 +26,14 @@ ] -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('PUBSUB_EMULATOR_HOST') if host: @@ -44,9 +49,9 @@ class SubscriberClient: def __init__( self, *, service_file: Optional[Union[str, IO[AnyStr]]] = None, token: Optional[Token] = None, session: Optional[Session] = None, - api_root: Optional[str] = None, + api_root: Optional[str] = None, api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self.session = AioSession(session, verify_ssl=not self._api_is_dev) self.token = token or Token( diff --git a/pubsub/poetry.rest.lock b/pubsub/poetry.rest.lock index b899ef604..d9f81e5b7 100644 --- a/pubsub/poetry.rest.lock +++ b/pubsub/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -373,13 +373,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -387,7 +387,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -444,13 +444,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -462,4 +462,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "d19d0b0b917079c4542e0b1291be9d0b2fb1e98c5960094383a41e0621ba87be" +content-hash = "a500ae410a982f07d8fd2d7a4b85dad43914457c85c11fcdf7848a74175b785b" diff --git a/pubsub/pyproject.rest.toml b/pubsub/pyproject.rest.toml index 703887250..7fb9fe5ea 100644 --- a/pubsub/pyproject.rest.toml +++ b/pubsub/pyproject.rest.toml @@ -27,8 +27,8 @@ gcloud-rest-auth = ">= 3.3.0, < 6.0.0" [tool.poetry.group.dev.dependencies] # aiohttp = "3.9.1" gcloud-rest-auth = { path = "../auth" } -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/pubsub/tests/unit/publisher_test.py b/pubsub/tests/unit/publisher_test.py new file mode 100644 index 000000000..80e71cff0 --- /dev/null +++ b/pubsub/tests/unit/publisher_test.py @@ -0,0 +1,63 @@ +from typing import Optional +from unittest.mock import AsyncMock +from unittest.mock import patch + +import pytest +from gcloud.aio.pubsub import PublisherClient + +# ========= +# client +# ========= + + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + async def _make_request(client_obj: PublisherClient, + should_have_auth_header: bool) -> None: + with patch.object( + client_obj.session, 'put', return_value=AsyncMock(), + ) as mock_req: + async with client_obj as client: + await client.create_topic('foobar') + assert mock_req.call_count == 1 + assert ( + 'Authorization' in mock_req.mock_calls[0].kwargs['headers'] + ) == should_have_auth_header + + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + await _make_request( + PublisherClient(token=_MockToken()), should_have_auth_header=True, + ) + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + await _make_request( + PublisherClient(api_root=api_root, token=_MockToken()), + should_have_auth_header=False, + ) + # If API specified to be dev, auth header should not be set + await _make_request( + PublisherClient(api_root=api_root, api_is_dev=True, + token=_MockToken()), + should_have_auth_header=False, + ) + # If API specified to not be dev, auth header should be set + await _make_request( + PublisherClient( + api_root=api_root, + api_is_dev=False, + token=_MockToken()), + should_have_auth_header=True, + ) diff --git a/pubsub/tests/unit/subscriber_test.py b/pubsub/tests/unit/subscriber_test.py index 707cde48d..11d60f7bd 100644 --- a/pubsub/tests/unit/subscriber_test.py +++ b/pubsub/tests/unit/subscriber_test.py @@ -1,5 +1,8 @@ # pylint: disable=redefined-outer-name -from gcloud.aio.auth import BUILD_GCLOUD_REST +# pylint: disable=too-many-lines +from typing import Optional + +from gcloud.aio.auth import BUILD_GCLOUD_REST # pylint:disable=no-name-in-module # pylint: disable=too-complex if BUILD_GCLOUD_REST: @@ -9,12 +12,13 @@ import asyncio import time import logging - from unittest.mock import call + from unittest.mock import call, AsyncMock from unittest.mock import MagicMock from unittest.mock import patch import pytest + from gcloud.aio.pubsub import SubscriberClient from gcloud.aio.pubsub.subscriber import AckDeadlineCache from gcloud.aio.pubsub.subscriber import acker from gcloud.aio.pubsub.subscriber import consumer @@ -944,3 +948,58 @@ async def test_subscribe_integrates_whole_chain( # verify that the subscriber shuts down gracefully with pytest.raises(asyncio.CancelledError): await asyncio.wait_for(subscribe_task, 1) + + # ========= + # client + # ========= + + class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + @pytest.mark.asyncio + async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + async def _make_request(client_obj: SubscriberClient, + should_have_auth_header: bool) -> None: + with patch.object( + client_obj.session, 'get', return_value=AsyncMock(), + ) as mock_req: + async with client_obj as client: + await client.get_subscription('foobar') + assert mock_req.call_count == 1 + assert ( + 'Authorization' in mock_req.mock_calls[0].kwargs['headers'] + ) == should_have_auth_header + + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should + # be set + await _make_request( + SubscriberClient(token=_MockToken()), should_have_auth_header=True, + ) + # If API root set and not otherwise specified, assume API is dev, so + # auth header should not be set + await _make_request( + SubscriberClient(api_root=api_root, token=_MockToken()), + should_have_auth_header=False, + ) + # If API specified to be dev, auth header should not be set + await _make_request( + SubscriberClient( + api_root=api_root, api_is_dev=True, token=_MockToken(), + ), + should_have_auth_header=False, + ) + # If API specified to not be dev, auth header should be set + await _make_request( + SubscriberClient( + api_root=api_root, api_is_dev=False, token=_MockToken(), + ), + should_have_auth_header=True, + ) diff --git a/storage/gcloud/aio/storage/storage.py b/storage/gcloud/aio/storage/storage.py index 492dae456..4c2c92fec 100644 --- a/storage/gcloud/aio/storage/storage.py +++ b/storage/gcloud/aio/storage/storage.py @@ -47,9 +47,14 @@ log = logging.getLogger(__name__) -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('STORAGE_EMULATOR_HOST') if host: @@ -159,9 +164,9 @@ class Storage: def __init__( self, *, service_file: Optional[Union[str, IO[AnyStr]]] = None, token: Optional[Token] = None, session: Optional[Session] = None, - api_root: Optional[str] = None, + api_root: Optional[str] = None, api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self._api_root_read = f'{self._api_root}/storage/v1/b' self._api_root_write = f'{self._api_root}/upload/storage/v1/b' diff --git a/storage/poetry.rest.lock b/storage/poetry.rest.lock index 784aae429..e54d98c2b 100644 --- a/storage/poetry.rest.lock +++ b/storage/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -356,13 +356,12 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.4.0" +version = "0.4.1" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, + {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, ] [package.dependencies] @@ -398,13 +397,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -412,7 +411,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -483,13 +482,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -501,4 +500,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "89adbe0dc6d7b9dda0ae8905f2a8f05751f902057778fe2d4eebdf37d9d6843b" +content-hash = "182dba584e3e8a3b736ebf654d85522f90f8033088ece730af3546d663b9203e" diff --git a/storage/pyproject.rest.toml b/storage/pyproject.rest.toml index 32a63d98b..bc5a20acc 100644 --- a/storage/pyproject.rest.toml +++ b/storage/pyproject.rest.toml @@ -21,15 +21,15 @@ classifiers = [ [tool.poetry.dependencies] python = ">= 3.8, < 4.0" -# aiofiles = ">= 0.6.0, < 24.0.0" +# aiofiles = ">= 0.6.0, < 25.0.0" gcloud-rest-auth = ">= 5.3.0, < 6.0.0" -pyasn1-modules = ">= 0.2.1, < 0.4.1" +pyasn1-modules = ">= 0.2.1, < 0.4.2" rsa = ">= 3.1.4, < 5.0.0" [tool.poetry.group.dev.dependencies] gcloud-rest-auth = { path = "../auth" } -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/storage/tests/unit/storage_test.py b/storage/tests/unit/storage_test.py index 4ffa66c55..2c150a433 100644 --- a/storage/tests/unit/storage_test.py +++ b/storage/tests/unit/storage_test.py @@ -1,5 +1,63 @@ -from gcloud.aio.storage import storage # pylint: disable=unused-import +from typing import Optional +from unittest.mock import AsyncMock +from unittest.mock import patch + +import pytest +from gcloud.aio.storage import Storage def test_importable(): assert True + + +# ========= +# client +# ========= + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + async def _make_request(client_obj: Storage, + should_have_auth_header: bool) -> None: + with patch.object( + client_obj.session, 'get', return_value=AsyncMock(), + ) as mock_req: + async with client_obj as client: + await client.list_objects('foobar') + assert mock_req.call_count == 1 + assert ( + 'Authorization' in mock_req.mock_calls[0].kwargs['headers'] + ) == should_have_auth_header + + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + await _make_request( + Storage(token=_MockToken()), should_have_auth_header=True, + ) + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + await _make_request( + Storage(api_root=api_root, token=_MockToken()), + should_have_auth_header=False, + ) + # If API specified to be dev, auth header should not be set + await _make_request( + Storage(api_root=api_root, api_is_dev=True, token=_MockToken()), + should_have_auth_header=False, + ) + # If API specified to not be dev, auth header should be set + await _make_request( + Storage(api_root=api_root, api_is_dev=False, token=_MockToken()), + should_have_auth_header=True, + ) diff --git a/taskqueue/gcloud/aio/taskqueue/queue.py b/taskqueue/gcloud/aio/taskqueue/queue.py index 6a0d35300..2d9f77fe4 100644 --- a/taskqueue/gcloud/aio/taskqueue/queue.py +++ b/taskqueue/gcloud/aio/taskqueue/queue.py @@ -29,9 +29,14 @@ log = logging.getLogger(__name__) -def init_api_root(api_root: Optional[str]) -> Tuple[bool, str]: +def init_api_root( + api_root: Optional[str], api_is_dev: Optional[bool], +) -> Tuple[bool, str]: if api_root: - return True, api_root + if api_is_dev is None: + # Assume a provided API root is dev unless otherwise specified + api_is_dev = True + return api_is_dev, api_root host = os.environ.get('CLOUDTASKS_EMULATOR_HOST') if host: @@ -49,9 +54,9 @@ def __init__( self, project: str, taskqueue: str, location: str = 'us-central1', service_file: Optional[Union[str, IO[AnyStr]]] = None, session: Optional[Session] = None, token: Optional[Token] = None, - api_root: Optional[str] = None, + api_root: Optional[str] = None, api_is_dev: Optional[bool] = None, ) -> None: - self._api_is_dev, self._api_root = init_api_root(api_root) + self._api_is_dev, self._api_root = init_api_root(api_root, api_is_dev) self._queue_path = ( f'projects/{project}/locations/{location}/queues/{taskqueue}' ) diff --git a/taskqueue/poetry.rest.lock b/taskqueue/poetry.rest.lock index dd243f865..4968db2ba 100644 --- a/taskqueue/poetry.rest.lock +++ b/taskqueue/poetry.rest.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -277,7 +277,7 @@ test = ["pytest (>=6)"] [[package]] name = "gcloud-rest-auth" -version = "5.3.1" +version = "5.3.2" description = "Python Client for Google Cloud Auth" optional = false python-versions = ">= 3.8, < 4.0" @@ -373,13 +373,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -387,7 +387,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -444,13 +444,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -462,4 +462,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">= 3.8, < 4.0" -content-hash = "79ce0c4ff45e43641dba48575ca865fdb16013d7547c807113a20e7f30c9cc80" +content-hash = "3b4e987c4d59eaa73d45671970bd384fb051f5909762b753a21de3c9584faf5e" diff --git a/taskqueue/pyproject.rest.toml b/taskqueue/pyproject.rest.toml index d23c06b2d..326a6df2b 100644 --- a/taskqueue/pyproject.rest.toml +++ b/taskqueue/pyproject.rest.toml @@ -26,8 +26,8 @@ gcloud-rest-auth = ">= 3.1.0, < 6.0.0" [tool.poetry.group.dev.dependencies] # aiohttp = "3.9.1" gcloud-rest-auth = { path = "../auth" } -pytest = "8.2.2" -# pytest-asyncio = "0.23.7" +pytest = "8.3.3" +# pytest-asyncio = "0.23.8" pytest-mock = "3.14.0" [[tool.poetry.source]] diff --git a/taskqueue/tests/unit/queue_test.py b/taskqueue/tests/unit/queue_test.py index 875316326..753b63234 100644 --- a/taskqueue/tests/unit/queue_test.py +++ b/taskqueue/tests/unit/queue_test.py @@ -1,5 +1,50 @@ -from gcloud.aio.taskqueue import queue # pylint: disable=unused-import +from typing import Optional + +import pytest +from gcloud.aio.taskqueue import PushQueue def test_importable(): assert True + + +# ========= +# client +# ========= + +class _MockToken: + @staticmethod + async def get() -> Optional[str]: + return 'Unit-Test-Bearer-Token' + + +@pytest.mark.asyncio +async def test_client_api_is_dev(): + """ + Test that the api_is_dev constructor parameter controls whether the + Authorization header is set on requests + """ + api_root = 'https://foobar/v1' + + # With no API root specified, assume API not dev, so auth header should be + # set + async with PushQueue('foo', 'bar', token=_MockToken()) as queue: + assert 'Authorization' in await queue.headers() + # If API root set and not otherwise specified, assume API is dev, so auth + # header should not be set + async with PushQueue( + 'foo', 'bar', api_root=api_root, token=_MockToken(), + ) as queue: + assert 'Authorization' not in await queue.headers() + # If API specified to be dev, auth header should not be set + async with PushQueue( + 'foo', 'bar', api_root=api_root, api_is_dev=True, + token=_MockToken(), + ) as queue: + assert 'Authorization' not in await queue.headers() + # If API specified to not be dev, auth header should be set + async with PushQueue( + 'foo', 'bar', api_root=api_root, api_is_dev=False, + token=_MockToken(), + ) as queue: + assert 'Authorization' in await queue.headers()