From aee3cd545cc546d5274fb3a86f72a34ca072949d Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Tue, 26 May 2026 21:53:45 +0000 Subject: [PATCH 01/10] feat(dea): define DataEngineeringAgentGenerator and integrate A2A SDK with GCP ADC Task 1.1: Define DataEngineeringAgentGenerator inheriting from QueryGenerator in data_engineering_agent.py. Task 1.2: Integrate A2A SDK and configure GcpAdcCredentialService to use GCP Application Default Credentials (ADC) for authentication. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- .../models/data_engineering_agent.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 evalbench/generators/models/data_engineering_agent.py diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py new file mode 100644 index 00000000..7279525d --- /dev/null +++ b/evalbench/generators/models/data_engineering_agent.py @@ -0,0 +1,45 @@ +import logging +from typing import Any, Dict + +from a2a.client import create_client, ClientConfig +from a2a.client.auth import AuthInterceptor, CredentialService +from a2a.client.client import ClientCallContext +from a2a.types import a2a_pb2 as pb +import google.auth +from google.auth.transport.requests import Request + +from .generator import QueryGenerator + + +class GcpAdcCredentialService(CredentialService): + """GCP Application Default Credentials (ADC) service for A2A SDK.""" + + async def get_credentials( + self, + security_scheme_name: str, + context: ClientCallContext | None, + ) -> str | None: + credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"]) + credentials.refresh(Request()) + token = credentials.token + print(f"🔑 [A2A Credential Service] Successfully retrieved GCP ADC token: {token[:10]}...") + return token + + +class DataEngineeringAgentGenerator(QueryGenerator): + """Data Engineering Agent (DEA) Query Generator.""" + + def __init__(self, querygenerator_config: Dict[str, Any]): + super().__init__(querygenerator_config) + self.name = "data_engineering_agent" + self.logger = logging.getLogger(__name__) + + # Task 1.2: Configure authentication + self.auth_interceptor = AuthInterceptor(GcpAdcCredentialService()) + print("✅ A2A AuthInterceptor successfully configured with GCP ADC Credential Service!") + + def generate_internal(self, prompt: str) -> str: + """Generates a response for the given prompt (A2A logic stub).""" + raise NotImplementedError("Task 1.3 A2A messaging logic is not yet implemented.") + + From 008abf96468b3fa5cc9d4b3deb8abea59fe3493f Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Wed, 27 May 2026 18:08:55 +0000 Subject: [PATCH 02/10] feat(dea): integrate A2A SDK dependency and add setup unit tests - Add `a2a-sdk>=1.0.3` to dependencies in `pyproject.toml` and update `uv.lock`. - Update `DataEngineeringAgentGenerator` to configure `endpoint`, `target_workspace` and clean up GCP ADC Credential Service logic with event loop safety (async refresh). - Register `DataEngineeringAgentGenerator` in the generators factory. - Add `evalbench/test/data_engineering_agent_test.py` to verify correct generator setup and configurations. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/generators/models/__init__.py | 3 + .../models/data_engineering_agent.py | 79 +++++++--- evalbench/test/data_engineering_agent_test.py | 31 ++++ pyproject.toml | 1 + uv.lock | 144 ++++++++++++++++++ 5 files changed, 240 insertions(+), 18 deletions(-) create mode 100644 evalbench/test/data_engineering_agent_test.py diff --git a/evalbench/generators/models/__init__.py b/evalbench/generators/models/__init__.py index 61913cde..98d2e144 100644 --- a/evalbench/generators/models/__init__.py +++ b/evalbench/generators/models/__init__.py @@ -10,6 +10,7 @@ from .gemini_cli import GeminiCliGenerator from .claude_code import ClaudeCodeGenerator from .codex_cli import CodexCliGenerator +from .data_engineering_agent import DataEngineeringAgentGenerator from util.config import load_yaml_config @@ -42,6 +43,8 @@ def get_generator(global_models, model_config_path: str, db: DB = None): model = ClaudeCodeGenerator(config) if config["generator"] == "codex_cli": model = CodexCliGenerator(config) + if config["generator"] == "data_engineering_agent": + model = DataEngineeringAgentGenerator(config) if not model: raise ValueError(f"Unknown Generator {config['generator']}") diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index 7279525d..0575bec8 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -1,45 +1,88 @@ +import asyncio import logging +import subprocess from typing import Any, Dict -from a2a.client import create_client, ClientConfig +from a2a.client import ClientCallContext, ClientConfig from a2a.client.auth import AuthInterceptor, CredentialService -from a2a.client.client import ClientCallContext -from a2a.types import a2a_pb2 as pb import google.auth +from google.auth.exceptions import DefaultCredentialsError, RefreshError from google.auth.transport.requests import Request from .generator import QueryGenerator class GcpAdcCredentialService(CredentialService): - """GCP Application Default Credentials (ADC) service for A2A SDK.""" + """GCP Application Default Credentials (ADC) service for A2A SDK. + + This provider intentionally only services OAuth/OAuth2 schemes. + """ + + def __init__(self): + self.logger = logging.getLogger(__name__) + self.credentials = None async def get_credentials( self, security_scheme_name: str, context: ClientCallContext | None, ) -> str | None: - credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"]) - credentials.refresh(Request()) - token = credentials.token - print(f"🔑 [A2A Credential Service] Successfully retrieved GCP ADC token: {token[:10]}...") - return token + if security_scheme_name.lower() not in ("oauth", "oauth2"): + raise ValueError( + f"GcpAdcCredentialService only services 'oauth' or 'oauth2' " + f"schemes, got '{security_scheme_name}'" + ) + + try: + if self.credentials is None: + self.credentials, _ = google.auth.default( + scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) + + if not self.credentials.valid: + # Move synchronous network call off the event loop + await asyncio.to_thread(self.credentials.refresh, Request()) + + self.logger.debug("Retrieved GCP ADC token successfully.") + return self.credentials.token + + except (DefaultCredentialsError, RefreshError) as e: + self.logger.error( + "Failed to retrieve or refresh GCP Application Default Credentials: %s", + e, + ) + return None + except Exception as e: + self.logger.exception( + "Unexpected error while fetching GCP ADC credentials: %s", e + ) + return None class DataEngineeringAgentGenerator(QueryGenerator): - """Data Engineering Agent (DEA) Query Generator.""" + """Data Engineering Agent (DEA) Query Generator using the A2A SDK.""" def __init__(self, querygenerator_config: Dict[str, Any]): super().__init__(querygenerator_config) self.name = "data_engineering_agent" + self.endpoint = querygenerator_config.get( + "endpoint", + "https://geminidataanalytics.googleapis.com/v1/a2a/projects/bq-dataworkeragent-test/locations/us-west4/agents/dataengineeringagent" + ) + self.target_workspace = querygenerator_config.get( + "target_workspace", + "projects/bq-dataworkeragent-test/locations/us-west4/repositories/agent_demo_dataform/workspaces/james_test_123" + ) self.logger = logging.getLogger(__name__) - - # Task 1.2: Configure authentication - self.auth_interceptor = AuthInterceptor(GcpAdcCredentialService()) - print("✅ A2A AuthInterceptor successfully configured with GCP ADC Credential Service!") - - def generate_internal(self, prompt: str) -> str: - """Generates a response for the given prompt (A2A logic stub).""" - raise NotImplementedError("Task 1.3 A2A messaging logic is not yet implemented.") + # Task 1.2: Configure AuthInterceptor with our custom CredentialService + self.auth_interceptor = AuthInterceptor(GcpAdcCredentialService()) + self.logger.info( + "A2A AuthInterceptor successfully configured with GcpAdcCredentialService." + ) + def generate_internal(self, prompt: str) -> subprocess.CompletedProcess: + """Stubbed messaging logic for WIP scaffolding (Task 1.3).""" + raise NotImplementedError( + "Task 1.3 DEA A2A messaging logic in generate_internal is not yet implemented." + ) diff --git a/evalbench/test/data_engineering_agent_test.py b/evalbench/test/data_engineering_agent_test.py new file mode 100644 index 00000000..eed54ad5 --- /dev/null +++ b/evalbench/test/data_engineering_agent_test.py @@ -0,0 +1,31 @@ +import os +import sys +from unittest.mock import MagicMock, patch +import pytest + +# Add generators path to system path +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from generators.models import get_generator +from generators.models.data_engineering_agent import DataEngineeringAgentGenerator + + +def test_data_engineering_agent_generator_setup(): + config = { + "generator": "data_engineering_agent", + "endpoint": "https://geminidataanalytics.googleapis.com/v1/a2a/projects/test/locations/us-west4/agents/dataengineeringagent", + "target_workspace": "projects/test/locations/us-west4/repositories/test-repo/workspaces/test-workspace", + } + + # Mock google.auth.default during initialization + with patch("google.auth.default") as mock_auth_default: + mock_creds = MagicMock() + mock_creds.valid = True + mock_auth_default.return_value = (mock_creds, "test-project") + + generator = DataEngineeringAgentGenerator(config) + + assert generator.name == "data_engineering_agent" + assert generator.endpoint == config["endpoint"] + assert generator.target_workspace == config["target_workspace"] + assert generator.auth_interceptor is not None diff --git a/pyproject.toml b/pyproject.toml index 9b151d48..eade4a43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "dbt-core", "dbt-bigquery", "dbt-postgres", + "a2a-sdk>=1.0.3", ] [tool.uv.sources] diff --git a/uv.lock b/uv.lock index 574543d6..9dab9662 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,26 @@ members = [ "viewer", ] +[[package]] +name = "a2a-sdk" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "culsans", marker = "python_full_version < '3.13'" }, + { name = "google-api-core" }, + { name = "googleapis-common-protos" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "json-rpc" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/35/8b7ac94f405f57c591925fa0afc105a0f797151876fffa666b57722eefa9/a2a_sdk-1.0.3.tar.gz", hash = "sha256:c57ddd910aece4a426ae26b8f0d0e8e2f3271a6adde974078075e4f600aaf628", size = 367155, upload-time = "2026-05-13T06:52:33.929Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/6f/ae79f8210f1ecd70e1c37c310a523b26f1d6da458d4c1365914bf1ea58e0/a2a_sdk-1.0.3-py3-none-any.whl", hash = "sha256:068e5b2ceb4e962ac61d9e1fd43ca0c1016b64f0c80d901f6e23420bc8a31a93", size = 235705, upload-time = "2026-05-13T06:52:31.88Z" }, +] + [[package]] name = "absl-py" version = "2.4.0" @@ -191,6 +211,20 @@ version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/a8/ca4c00b319b877d29aa792cdd4ae3fb2a9f57268d94708a637abe9ae58c5/aiologger-0.7.0.tar.gz", hash = "sha256:7a4d5c91b836b61e842a791071786a3d80d6b6fa46fb8fd8e73391253ecb72ac", size = 20485, upload-time = "2022-10-05T01:03:22.199Z" } +[[package]] +name = "aiologic" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sniffio", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "wrapt", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/13/50b91a3ea6b030d280d2654be97c48b6ed81753a50286ee43c646ba36d3c/aiologic-0.16.0.tar.gz", hash = "sha256:c267ccbd3ff417ec93e78d28d4d577ccca115d5797cdbd16785a551d9658858f", size = 225952, upload-time = "2025-11-27T23:48:41.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/27/206615942005471499f6fbc36621582e24d0686f33c74b2d018fcfd4fe67/aiologic-0.16.0-py3-none-any.whl", hash = "sha256:e00ce5f68c5607c864d26aec99c0a33a83bdf8237aa7312ffbb96805af67d8b6", size = 135193, upload-time = "2025-11-27T23:48:40.099Z" }, +] + [[package]] name = "aiosignal" version = "1.4.0" @@ -645,6 +679,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, ] +[[package]] +name = "culsans" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiologic", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/5d/9fb19fb38f6d6120422064279ea5532e22b84aa2be8831d49607194feda3/culsans-0.11.0-py3-none-any.whl", hash = "sha256:278d118f63fc75b9db11b664b436a1b83cc30d9577127848ba41420e66eb5a47", size = 21811, upload-time = "2025-12-31T23:15:37.189Z" }, +] + [[package]] name = "daff" version = "1.4.2" @@ -1756,6 +1803,7 @@ name = "google-evalbench" version = "1.7.1" source = { editable = "." } dependencies = [ + { name = "a2a-sdk" }, { name = "absl-py" }, { name = "aiologger" }, { name = "anthropic", extra = ["vertex"] }, @@ -1806,6 +1854,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "a2a-sdk", specifier = ">=1.0.3" }, { name = "absl-py" }, { name = "aiologger" }, { name = "anthropic", extras = ["vertex"] }, @@ -2365,6 +2414,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, ] +[[package]] +name = "json-rpc" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/9e/59f4a5b7855ced7346ebf40a2e9a8942863f644378d956f68bcef2c88b90/json-rpc-1.15.0.tar.gz", hash = "sha256:e6441d56c1dcd54241c937d0a2dcd193bdf0bdc539b5316524713f554b7f85b9", size = 28854, upload-time = "2023-06-11T09:45:49.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9e/820c4b086ad01ba7d77369fb8b11470a01fac9b4977f02e18659cf378b6b/json_rpc-1.15.0-py2.py3-none-any.whl", hash = "sha256:4a4668bbbe7116feb4abbd0f54e64a4adcf4b8f648f19ffa0848ad0f6606a9bf", size = 39450, upload-time = "2023-06-11T09:45:47.136Z" }, +] + [[package]] name = "jsonschema" version = "4.26.0" @@ -5010,6 +5068,92 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, ] +[[package]] +name = "wrapt" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/9f/06263fcd8ad6c405f05a3905fd7a84dd3176eb5ad46e44bccc0cd16348bb/wrapt-2.2.1.tar.gz", hash = "sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9", size = 127620, upload-time = "2026-05-22T14:49:43.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/8b/84bc1ea68b620fe0e2696a8cff07e82f4b962d952ab14efee8955997bb70/wrapt-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0f68f478004475d97906686e702ddbddeaf717c0b68ad2794384308f2dc713ae", size = 80093, upload-time = "2026-05-22T14:47:27.074Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/64ec81194a0bc708d9720174c998c8a32116e82b5b32c04e20a7fe01176c/wrapt-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e422b2d647a65d6b080cad5accd09055d3809bdff00c76fba8dca00ca935572a", size = 81183, upload-time = "2026-05-22T14:47:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/3d186944aae923631d1def58f4c4ff8f0b6309906afc0b6978de3e69b3e0/wrapt-2.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:036dfb40128819a751c6f451c6b9c10172c49e4c401aebcdb8ecf2aec1683598", size = 152494, upload-time = "2026-05-22T14:47:30.583Z" }, + { url = "https://files.pythonhosted.org/packages/01/d1/6b3d0ea995b867d2862aad5619bd5e17de09a9d64a821f46832dcd272d40/wrapt-2.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09ac16c081bebfd15d8e4dfa5bdc805990bbd52249ecff22530da7a129d6120b", size = 154310, upload-time = "2026-05-22T14:47:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4b/37ecb90a8c3753e580327fb40731a984b754e3df65d2ef932bf359fe4adc/wrapt-2.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07be671fa8875971222b0ba9059ed8b4dc738631122feba17c93aa36b4213e9a", size = 149002, upload-time = "2026-05-22T14:47:34.021Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d0/918884d9dfa84d0d135b42a51c00910f5c5447fe7a5e211a8e16ac324dd4/wrapt-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93fc2bf40cd7f4a0256010dce073d44eeb4a351b9bca94d0477ce2b6e62532b3", size = 153185, upload-time = "2026-05-22T14:47:35.722Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/382299d8ced610b29b59b099a89eda821e8c489aa152b7183748ac83f32a/wrapt-2.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba519b2d765df9871a25879e6f7fa78948ea59a2a31f9c1a257e34b651994afc", size = 148040, upload-time = "2026-05-22T14:47:37.052Z" }, + { url = "https://files.pythonhosted.org/packages/6c/46/62a79b79e35bbebb1207ca5d15b81192f37f20cc5659cf4e3ce955b7fcc8/wrapt-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9011395be8db1827d106c6449b4bb6dd17e331ff6ec521f227e4588f1c78e46f", size = 151773, upload-time = "2026-05-22T14:47:38.713Z" }, + { url = "https://files.pythonhosted.org/packages/a1/db/95c152151d206d4b430516c89725306e92484072f38e65492afde63f6d19/wrapt-2.2.1-cp310-cp310-win32.whl", hash = "sha256:a8f7176b83664af44567e9cc06e0d3827823fcc1a5e52307ebb8ac3aa95860b9", size = 77393, upload-time = "2026-05-22T14:47:40.061Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/882d50452c6fbd13f24fe5d2644b97cdad2565a7e1522cbb6312de8a52cf/wrapt-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7f513d3185e6fec82d0c3518f2e6365d8b4e49f5f45f29640d5162d56a23b54", size = 80350, upload-time = "2026-05-22T14:47:41.194Z" }, + { url = "https://files.pythonhosted.org/packages/58/0f/148376523b4e370692286a9ba14d5715cf3c5b86da3bd3630926367b6b73/wrapt-2.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:44255c84bc57554fed822e83e70036b51afa9edb56fc7ca56c54410ece7898c9", size = 79149, upload-time = "2026-05-22T14:47:42.835Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ac/4370bde262c0e633e6c4f0e56d55095710024cf9a5cecc20c59a10de483c/wrapt-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd57607acc85678925940bd5df0385ff8332083a32fa8d7a43f8767f4997263c", size = 80321, upload-time = "2026-05-22T14:47:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/eb/79/b8ff3a61e71babf58a8cf4c0d63358e8bad383e15bf7f35e62d2f6b6e4a4/wrapt-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ae574d65c9fa8e86f64f6a7c2668f9fcd507b183e0e577619f504b883cb0a6c", size = 81216, upload-time = "2026-05-22T14:47:45.243Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fd/c0cac1f77c9c4f6fe58a920ca632ce379bb8be928720e11e8d73de28a5e9/wrapt-2.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a04c28c10ba7fd12842b109d2edb0678872a2fe65277ca4ff06a0d61edee245", size = 159208, upload-time = "2026-05-22T14:47:47.176Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4f/744132a7b2fbefa6b81118ec5942eca5fc2e9a129f9055a0c5e46885a549/wrapt-2.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e2f02472a1cbbf3884b365714a810b5947134a95ad6952b554cb8cce9d492b0", size = 160322, upload-time = "2026-05-22T14:47:49.04Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/b7cd9a22a06cf93e6482904ee6afc956248983553593fd1009296d1b3b31/wrapt-2.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac2745950b2bff80219c15ebf2fa9d8427eba7e249739f97e55c9d169e47e9e1", size = 153243, upload-time = "2026-05-22T14:47:50.386Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4a/eb79423192015f46f0db2872e7e04a3dde8d359b83411e8959e7c9287eaa/wrapt-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67a97e5b6c457f0cd3cfc19ebb2d84463e60c3ece754cc831e4281a3ca29bb18", size = 159231, upload-time = "2026-05-22T14:47:51.753Z" }, + { url = "https://files.pythonhosted.org/packages/ec/dc/435015b58ce33c6fc4104158fa91ddb0e809ab03a5751fb7465d1d461456/wrapt-2.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c803a3d331796255af51ba2c79ed0ac8275865b516c09e61f248d1e7aff31ce9", size = 152351, upload-time = "2026-05-22T14:47:53.214Z" }, + { url = "https://files.pythonhosted.org/packages/77/ac/5d203f98df8fd136b95c5227139aea02d34505e18baf812d0c005df61963/wrapt-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b984d1eb252145d6302c1dbd5e87fc6d404d45531447c84eadec04bf1fcb027", size = 158347, upload-time = "2026-05-22T14:47:54.982Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/a92427dbdc74e54c1674abbed27e61b2cb5e7a94441b8c1270c70671d928/wrapt-2.2.1-cp311-cp311-win32.whl", hash = "sha256:8a983a603a18c8708f024f7f6991b2e66159219abbf894634c5056243c55f3cd", size = 77562, upload-time = "2026-05-22T14:47:56.275Z" }, + { url = "https://files.pythonhosted.org/packages/c8/56/987b9c13b3e1c1a3c6de71284076f996b79caec90e75a87c044a40c23db9/wrapt-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:9c210a6994b21aa9b29e81c8d11560e8fdab54c117e9cff37870d0a27bde1343", size = 80616, upload-time = "2026-05-22T14:47:57.854Z" }, + { url = "https://files.pythonhosted.org/packages/7e/25/d01f560888d99d94a959c85533de349ce68d71ace3f2591d6ea8f632cfed/wrapt-2.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:401229e9d63ca09f9b8891ecf83798d26c11bbb445d11ed9f1836b6d4585b38a", size = 79025, upload-time = "2026-05-22T14:47:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/bfae7b9401583b6d05938cd16dedc43857d96da2f8a3d50d78cc515bf6ff/wrapt-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0", size = 81021, upload-time = "2026-05-22T14:48:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/26/58/80f6a6599f933f4caecc1cb3ee88a04faf81e8b9bddbd6109c688dd63e0f/wrapt-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8", size = 81692, upload-time = "2026-05-22T14:48:01.49Z" }, + { url = "https://files.pythonhosted.org/packages/17/93/fb357cc7847c58a8ae790be718903afa81a28d23e642c843dc4129e8a0b2/wrapt-2.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e", size = 169364, upload-time = "2026-05-22T14:48:02.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0b/76b601ee309a8bd556af0eecb184394c20b3c49aa9c8e085aa1ffacc2568/wrapt-2.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926", size = 171079, upload-time = "2026-05-22T14:48:04.22Z" }, + { url = "https://files.pythonhosted.org/packages/cd/87/ee3f32d5658e3e26d3e0e457922b47a36dd3bfbdfee7f97bb3e802344a66/wrapt-2.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624", size = 160205, upload-time = "2026-05-22T14:48:05.553Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/ae2fd64277a67f5d7bffcf2d05eea1e476263fb2a072baf0b0129ab85984/wrapt-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710", size = 168922, upload-time = "2026-05-22T14:48:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f3/2d541a060c5bbafb9400bca4917e4d78bfd1f239f404782c86831a8f6b29/wrapt-2.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f", size = 158388, upload-time = "2026-05-22T14:48:08.629Z" }, + { url = "https://files.pythonhosted.org/packages/1d/68/8d92c8800c57e93cb116ae9e9d6cbafc34fade5ee9f9107b6f203fb4dc35/wrapt-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797", size = 167682, upload-time = "2026-05-22T14:48:10.042Z" }, + { url = "https://files.pythonhosted.org/packages/30/72/83ea3790ea352439442349388e29ff07b76e0686265f9088bbb505d1608d/wrapt-2.2.1-cp312-cp312-win32.whl", hash = "sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052", size = 77857, upload-time = "2026-05-22T14:48:11.782Z" }, + { url = "https://files.pythonhosted.org/packages/ef/cb/99450668dd3502d62a54a1c8aa56e44f34cb8c1261b381cfe2e7926c3b75/wrapt-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5", size = 80825, upload-time = "2026-05-22T14:48:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/87512881be64e743f9ee4c66f4cbe8e884974bef2a5989af71f999653ac7/wrapt-2.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579", size = 79087, upload-time = "2026-05-22T14:48:14.323Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/a1b08f8f4fac8cbb156fa51cf64ee2c7f7f74f9875ba3cf70b3c58368694/wrapt-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb", size = 80831, upload-time = "2026-05-22T14:48:15.598Z" }, + { url = "https://files.pythonhosted.org/packages/54/ce/57890814991446a845e09b3445ce8b694f27eb0577004f2c2a36a9772ed4/wrapt-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80", size = 81375, upload-time = "2026-05-22T14:48:17.071Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/08d7a6c76ac4493bdb668205ee9c1de1bd5daca61717c3e9aa49b4c01499/wrapt-2.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a", size = 167417, upload-time = "2026-05-22T14:48:18.303Z" }, + { url = "https://files.pythonhosted.org/packages/62/ce/f1ccbee7a1bfe5cdc6b3da6bab4b45713d628b9294da32a39f563d648140/wrapt-2.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474", size = 166948, upload-time = "2026-05-22T14:48:19.768Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/f85d48d1cd4869aee6704028d257d740a47c1c467b457ce396b4b5b55d07/wrapt-2.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143", size = 158148, upload-time = "2026-05-22T14:48:21.96Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5c/93939ad11d4a12358ab1aab219a2ef5efa5612e0db6b9fc65af8af1a891b/wrapt-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a", size = 165905, upload-time = "2026-05-22T14:48:23.373Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/b8c2aa89862ff58605934d7abf4b70e6a5a1c33df96656f49035ccdf1c8a/wrapt-2.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9", size = 156712, upload-time = "2026-05-22T14:48:24.767Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/bf00a7b02239c12bb02ddcc3c0b971bfcc36e578c5a44f1ccfef5b458545/wrapt-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31", size = 166560, upload-time = "2026-05-22T14:48:26.83Z" }, + { url = "https://files.pythonhosted.org/packages/fe/93/6390ca9c5b787683cef588d04f57c8d41b9a2323b5597a65f18638c90ef2/wrapt-2.2.1-cp313-cp313-win32.whl", hash = "sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337", size = 77817, upload-time = "2026-05-22T14:48:28.221Z" }, + { url = "https://files.pythonhosted.org/packages/97/73/ce10f0e71c0cfaa1a65faadb8efd4852028b3bb9ba28932b8889df769d38/wrapt-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215", size = 80736, upload-time = "2026-05-22T14:48:30.139Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4c/89f4a6818fafbbd840330e4fa3873073e1bfc166133a64cac7f8fde7a5e3/wrapt-2.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f", size = 79099, upload-time = "2026-05-22T14:48:31.405Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f2/9a8741c46f8c208ac0a45b25ba170bcb4fb72a2781d5fb97dbd7b6be73cb/wrapt-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8", size = 82802, upload-time = "2026-05-22T14:48:33.307Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0d/e9c855716a3705eef1416456bdf062b60620726fdc59428ff670fc3c60dc/wrapt-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8", size = 83329, upload-time = "2026-05-22T14:48:34.593Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d6/a88f1c13112b7831adac75cea65d8310e0d696d570c8961844c90a57b865/wrapt-2.2.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d", size = 202937, upload-time = "2026-05-22T14:48:35.859Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/e29d54aef06a4d898a5b8a25589a0b3769bde454f922fad8f6f89fbfb650/wrapt-2.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27", size = 209997, upload-time = "2026-05-22T14:48:38.153Z" }, + { url = "https://files.pythonhosted.org/packages/2a/91/e4454263516cf0e12640912fbca9a83654e424f0a6ddb79f5cd7ce14bf33/wrapt-2.2.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440", size = 194856, upload-time = "2026-05-22T14:48:39.69Z" }, + { url = "https://files.pythonhosted.org/packages/de/d0/fe0ee202286afdf4a7f77dd29f195703145764d572aec209c5086e57d924/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e", size = 205654, upload-time = "2026-05-22T14:48:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/23/b6/87d860dfc6460c246af70b1fd5c8b76df77571b42a493459423ded94fd7d/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b", size = 192206, upload-time = "2026-05-22T14:48:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/46/3eea8cde077d985f239a38c0257087b8064fd9ee9b1a99e282d2c86da4ef/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394", size = 198428, upload-time = "2026-05-22T14:48:46.319Z" }, + { url = "https://files.pythonhosted.org/packages/18/dc/b927ee9c7fc67adc3a5658f246a0d275425eb840ba36e7b702e70f18bde8/wrapt-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562", size = 79448, upload-time = "2026-05-22T14:48:47.901Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b3/fd30b473fe498c70e6b9a5f328b8d3fbaf1b8c3c481465f59724bba8eb70/wrapt-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53", size = 83021, upload-time = "2026-05-22T14:48:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/96c39153a8737a6e9aa85adef254ac4195bea3f2d24efc60472ccc3c9e2e/wrapt-2.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e", size = 80295, upload-time = "2026-05-22T14:48:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/11d7f34ebbf3231bc907a3e6d5ee051b14d034c1bc7b65a97d5cc00516df/wrapt-2.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab", size = 80879, upload-time = "2026-05-22T14:48:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/13/3c/b74cfd984cef560b900fb1a727af20352d89e1f06bf2e1114dd3f00f5f5a/wrapt-2.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c", size = 81462, upload-time = "2026-05-22T14:48:53.18Z" }, + { url = "https://files.pythonhosted.org/packages/15/a3/7c8f704b8dc07dfe0a5d01c2edbfd88317aa8e5e3fa7c743eb7a085ae767/wrapt-2.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c", size = 167251, upload-time = "2026-05-22T14:48:54.562Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/a34d1888d97247da6c2ff6118c3a721c73ed8cc4dd198c00208bb73b6f80/wrapt-2.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e", size = 166316, upload-time = "2026-05-22T14:48:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d7/72ffaeb01eebc704afe3fb99e840480f4bda45f0fa66e3381b6a39251c8f/wrapt-2.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f", size = 157952, upload-time = "2026-05-22T14:48:57.924Z" }, + { url = "https://files.pythonhosted.org/packages/24/5b/36f5d6b024e4edfdd90b140742d11ebcf7836daf5c9daf326c55c24db412/wrapt-2.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508", size = 166130, upload-time = "2026-05-22T14:48:59.384Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/9296d9e97bfdef5483dfcc859d57b095b257144b2bc5300ab521e06f4bc7/wrapt-2.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5", size = 156604, upload-time = "2026-05-22T14:49:00.921Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/16953929ed6776175720e58fc966e779926d8d71e2c7b2273230590ca71f/wrapt-2.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283", size = 166007, upload-time = "2026-05-22T14:49:02.332Z" }, + { url = "https://files.pythonhosted.org/packages/b9/73/20ee58c0612dae7c31131a7095345812ed2c7b389019e175f68cde34e5b4/wrapt-2.2.1-cp314-cp314-win32.whl", hash = "sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243", size = 78327, upload-time = "2026-05-22T14:49:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/22/b3/ef7c3295d02e0448a71c639a36a057f46d524d057c9486291a7a3039e65c/wrapt-2.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b", size = 81144, upload-time = "2026-05-22T14:49:05.093Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dc/7bdf336953f99f4ceb0a584bb8870e42c8f26f93ea10c87834dad62f1668/wrapt-2.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36", size = 79569, upload-time = "2026-05-22T14:49:06.413Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/6dfae80150ff1919c356d1dd528f049bcdfaae29b4d284bc957e022caef4/wrapt-2.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188", size = 82892, upload-time = "2026-05-22T14:49:07.925Z" }, + { url = "https://files.pythonhosted.org/packages/82/7b/4e34766a7d7804ffce9e71befe47e9b3225dc350c49c94493c4ab39fd3a5/wrapt-2.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199", size = 83333, upload-time = "2026-05-22T14:49:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/9d/57/0b34db3e8de44ccfece62d7b337abd1631dd810f5adc5f3db571727836b5/wrapt-2.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413", size = 202899, upload-time = "2026-05-22T14:49:10.572Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/ac0c459f154b99d92789a6cba7ca727185b83513b986f8ec7fe2aacddcbf/wrapt-2.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956", size = 209986, upload-time = "2026-05-22T14:49:12.229Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/77e37ff33ad018fa81ade52c25fa327b80b56f81d734279a63614fcb4cbc/wrapt-2.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e", size = 194893, upload-time = "2026-05-22T14:49:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9d/7ea651d1ab032fc5fa222fbec91d0f8a1397f6ae04ebb93fa7219aa921d7/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85", size = 205636, upload-time = "2026-05-22T14:49:15.714Z" }, + { url = "https://files.pythonhosted.org/packages/09/af/8e88031a701275b9085c54e64bc88c0b1cd55c77eadd400691c371cd76c4/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181", size = 192267, upload-time = "2026-05-22T14:49:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a8/e657ca876b06710194f243d81c4b0896ade646e244bdbec2d87c8c56a8bd/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a", size = 198378, upload-time = "2026-05-22T14:49:18.785Z" }, + { url = "https://files.pythonhosted.org/packages/c8/59/822efe4ea722a3961331bfa35b7d90937790d2c20f0616de1997ccc3aebd/wrapt-2.2.1-cp314-cp314t-win32.whl", hash = "sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85", size = 80226, upload-time = "2026-05-22T14:49:20.264Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/2a7dc5f6abb2fca0b6e1610e120419f603650aceb4f1d3ac4cae0354e162/wrapt-2.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50", size = 83835, upload-time = "2026-05-22T14:49:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" }, +] + [[package]] name = "yarl" version = "1.22.0" From cbad0bc0a39dca345a4555c40b6bbb195f87e5e2 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Wed, 27 May 2026 20:47:42 +0000 Subject: [PATCH 03/10] refactor(dea): remove hardcoded defaults and add config validation - Remove hardcoded test URLs for `endpoint` and `target_workspace` from `DataEngineeringAgentGenerator`. - Raise `ValueError` if either configuration key is missing or empty. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- .../models/data_engineering_agent.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index 0575bec8..ed9f81ed 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -48,7 +48,8 @@ async def get_credentials( except (DefaultCredentialsError, RefreshError) as e: self.logger.error( - "Failed to retrieve or refresh GCP Application Default Credentials: %s", + "Failed to retrieve or refresh GCP Application Default " + "Credentials: %s", e, ) return None @@ -65,24 +66,34 @@ class DataEngineeringAgentGenerator(QueryGenerator): def __init__(self, querygenerator_config: Dict[str, Any]): super().__init__(querygenerator_config) self.name = "data_engineering_agent" - self.endpoint = querygenerator_config.get( - "endpoint", - "https://geminidataanalytics.googleapis.com/v1/a2a/projects/bq-dataworkeragent-test/locations/us-west4/agents/dataengineeringagent" - ) + self.endpoint = querygenerator_config.get("endpoint", "") self.target_workspace = querygenerator_config.get( - "target_workspace", - "projects/bq-dataworkeragent-test/locations/us-west4/repositories/agent_demo_dataform/workspaces/james_test_123" + "target_workspace", "" ) + + if not self.endpoint: + raise ValueError( + "Configuration key 'endpoint' is required for " + "DataEngineeringAgentGenerator." + ) + if not self.target_workspace: + raise ValueError( + "Configuration key 'target_workspace' is required for " + "DataEngineeringAgentGenerator." + ) + self.logger = logging.getLogger(__name__) # Task 1.2: Configure AuthInterceptor with our custom CredentialService self.auth_interceptor = AuthInterceptor(GcpAdcCredentialService()) self.logger.info( - "A2A AuthInterceptor successfully configured with GcpAdcCredentialService." + "A2A AuthInterceptor successfully configured with " + "GcpAdcCredentialService." ) def generate_internal(self, prompt: str) -> subprocess.CompletedProcess: """Stubbed messaging logic for WIP scaffolding (Task 1.3).""" raise NotImplementedError( - "Task 1.3 DEA A2A messaging logic in generate_internal is not yet implemented." + "Task 1.3 DEA A2A messaging logic in generate_internal is " + "not yet implemented." ) From e1bc08701304a55e968053118ff2623e2b21b433 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Wed, 27 May 2026 22:32:56 +0000 Subject: [PATCH 04/10] style: remove comments from data_engineering_agent.py TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/generators/models/data_engineering_agent.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index ed9f81ed..fde5be3a 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -40,7 +40,6 @@ async def get_credentials( ) if not self.credentials.valid: - # Move synchronous network call off the event loop await asyncio.to_thread(self.credentials.refresh, Request()) self.logger.debug("Retrieved GCP ADC token successfully.") @@ -84,7 +83,6 @@ def __init__(self, querygenerator_config: Dict[str, Any]): self.logger = logging.getLogger(__name__) - # Task 1.2: Configure AuthInterceptor with our custom CredentialService self.auth_interceptor = AuthInterceptor(GcpAdcCredentialService()) self.logger.info( "A2A AuthInterceptor successfully configured with " From 61b7009724925cc1a97ec02c8172e36835b957a1 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Thu, 28 May 2026 17:41:29 +0000 Subject: [PATCH 05/10] test(dea): verify credential scheme check and clean up imports - Add a new unit test `test_get_credentials_invalid_scheme` verifying that `GcpAdcCredentialService` raises `ValueError` for unsupported auth schemes. - Replace absolute generator import in `evalbench/generators/models/__init__.py` with a relative one. - Clean up unused imports in `data_engineering_agent.py`. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/generators/models/__init__.py | 2 +- .../generators/models/data_engineering_agent.py | 6 +++--- evalbench/test/data_engineering_agent_test.py | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/evalbench/generators/models/__init__.py b/evalbench/generators/models/__init__.py index 98d2e144..9bf94fe1 100644 --- a/evalbench/generators/models/__init__.py +++ b/evalbench/generators/models/__init__.py @@ -1,6 +1,6 @@ from .alloydb_ai_nl import AlloyDBGenerator from databases import DB -from generators.models.generator import QueryGenerator +from .generator import QueryGenerator from .gemini import GeminiGenerator from .passthrough import NOOPGenerator from .grpc_proxy import GrpcProxyModel diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index fde5be3a..d5520697 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -1,9 +1,9 @@ import asyncio import logging import subprocess -from typing import Any, Dict +from typing import Any -from a2a.client import ClientCallContext, ClientConfig +from a2a.client import ClientCallContext from a2a.client.auth import AuthInterceptor, CredentialService import google.auth from google.auth.exceptions import DefaultCredentialsError, RefreshError @@ -62,7 +62,7 @@ async def get_credentials( class DataEngineeringAgentGenerator(QueryGenerator): """Data Engineering Agent (DEA) Query Generator using the A2A SDK.""" - def __init__(self, querygenerator_config: Dict[str, Any]): + def __init__(self, querygenerator_config: dict[str, Any]): super().__init__(querygenerator_config) self.name = "data_engineering_agent" self.endpoint = querygenerator_config.get("endpoint", "") diff --git a/evalbench/test/data_engineering_agent_test.py b/evalbench/test/data_engineering_agent_test.py index eed54ad5..718f5bd1 100644 --- a/evalbench/test/data_engineering_agent_test.py +++ b/evalbench/test/data_engineering_agent_test.py @@ -7,7 +7,10 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from generators.models import get_generator -from generators.models.data_engineering_agent import DataEngineeringAgentGenerator +from generators.models.data_engineering_agent import ( + DataEngineeringAgentGenerator, + GcpAdcCredentialService, +) def test_data_engineering_agent_generator_setup(): @@ -29,3 +32,14 @@ def test_data_engineering_agent_generator_setup(): assert generator.endpoint == config["endpoint"] assert generator.target_workspace == config["target_workspace"] assert generator.auth_interceptor is not None + + +@pytest.mark.anyio +async def test_get_credentials_invalid_scheme(): + service = GcpAdcCredentialService() + + with pytest.raises(ValueError) as excinfo: + await service.get_credentials("basic", None) + + assert "only services 'oauth' or 'oauth2'" in str(excinfo.value) + From b4bcb2af4fa05cdd6c3ba92ea2b994463355f8b1 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Thu, 28 May 2026 17:44:25 +0000 Subject: [PATCH 06/10] fix(dea): revert relative import of QueryGenerator in factory - Revert relative import of `QueryGenerator` back to absolute import `generators.models.generator.QueryGenerator` in `evalbench/generators/models/__init__.py` to prevent runtime package resolution issues. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/generators/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evalbench/generators/models/__init__.py b/evalbench/generators/models/__init__.py index 9bf94fe1..98d2e144 100644 --- a/evalbench/generators/models/__init__.py +++ b/evalbench/generators/models/__init__.py @@ -1,6 +1,6 @@ from .alloydb_ai_nl import AlloyDBGenerator from databases import DB -from .generator import QueryGenerator +from generators.models.generator import QueryGenerator from .gemini import GeminiGenerator from .passthrough import NOOPGenerator from .grpc_proxy import GrpcProxyModel From d7525780b76482752966879ccc37c558617b9b88 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Thu, 28 May 2026 19:02:50 +0000 Subject: [PATCH 07/10] feat(dea): throw auth errors and add error resilience tests - Update `GcpAdcCredentialService` to propagate `DefaultCredentialsError` and `RefreshError` up instead of catching them silently and returning `None`. - Add `test_generator_setup_missing_endpoint` and `test_generator_setup_missing_workspace` in `data_engineering_agent_test.py`. - Add `test_get_credentials_error_resiliency_default` and `test_get_credentials_error_resiliency_refresh` to verify error propagation. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- .../models/data_engineering_agent.py | 4 +- evalbench/test/data_engineering_agent_test.py | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index d5520697..cab50512 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -51,12 +51,12 @@ async def get_credentials( "Credentials: %s", e, ) - return None + raise except Exception as e: self.logger.exception( "Unexpected error while fetching GCP ADC credentials: %s", e ) - return None + raise class DataEngineeringAgentGenerator(QueryGenerator): diff --git a/evalbench/test/data_engineering_agent_test.py b/evalbench/test/data_engineering_agent_test.py index 718f5bd1..ea488330 100644 --- a/evalbench/test/data_engineering_agent_test.py +++ b/evalbench/test/data_engineering_agent_test.py @@ -2,6 +2,7 @@ import sys from unittest.mock import MagicMock, patch import pytest +from google.auth.exceptions import DefaultCredentialsError, RefreshError # Add generators path to system path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -43,3 +44,48 @@ async def test_get_credentials_invalid_scheme(): assert "only services 'oauth' or 'oauth2'" in str(excinfo.value) + +def test_generator_setup_missing_endpoint(): + config = { + "generator": "data_engineering_agent", + "target_workspace": "projects/test-workspace", + } + with pytest.raises(ValueError) as excinfo: + DataEngineeringAgentGenerator(config) + assert "endpoint' is required" in str(excinfo.value) + + +def test_generator_setup_missing_workspace(): + config = { + "generator": "data_engineering_agent", + "endpoint": "https://geminidataanalytics.googleapis.com/v1/a2a/projects/test/locations/us-west4/agents/dataengineeringagent", + } + with pytest.raises(ValueError) as excinfo: + DataEngineeringAgentGenerator(config) + assert "target_workspace' is required" in str(excinfo.value) + + +@pytest.mark.anyio +@patch("google.auth.default") +async def test_get_credentials_error_resiliency_default(mock_auth_default): + mock_auth_default.side_effect = DefaultCredentialsError("Credentials missing.") + service = GcpAdcCredentialService() + + with pytest.raises(DefaultCredentialsError): + await service.get_credentials("oauth", None) + + +@pytest.mark.anyio +@patch("google.auth.default") +async def test_get_credentials_error_resiliency_refresh(mock_auth_default): + mock_creds = MagicMock() + mock_creds.valid = False + mock_creds.refresh.side_effect = RefreshError("Network timed out.") + mock_auth_default.return_value = (mock_creds, "test-project") + + service = GcpAdcCredentialService() + + with pytest.raises(RefreshError): + await service.get_credentials("oauth", None) + + From 3c4e234e1816ac35b1a58a583f45fcb24f4bce23 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Thu, 28 May 2026 20:23:23 +0000 Subject: [PATCH 08/10] style: resolve all pycodestyle violations in tests - Add `# noqa: E402` to imports in `data_engineering_agent_test.py` to support custom `sys.path` modification before importing generators. - Wrap long test URLs using parenthesized string concatenation (E501). - Remove trailing blank line at EOF (W391). TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/test/data_engineering_agent_test.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/evalbench/test/data_engineering_agent_test.py b/evalbench/test/data_engineering_agent_test.py index ea488330..5cbfb44d 100644 --- a/evalbench/test/data_engineering_agent_test.py +++ b/evalbench/test/data_engineering_agent_test.py @@ -7,8 +7,8 @@ # Add generators path to system path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from generators.models import get_generator -from generators.models.data_engineering_agent import ( +from generators.models import get_generator # noqa: E402 +from generators.models.data_engineering_agent import ( # noqa: E402 DataEngineeringAgentGenerator, GcpAdcCredentialService, ) @@ -17,8 +17,15 @@ def test_data_engineering_agent_generator_setup(): config = { "generator": "data_engineering_agent", - "endpoint": "https://geminidataanalytics.googleapis.com/v1/a2a/projects/test/locations/us-west4/agents/dataengineeringagent", - "target_workspace": "projects/test/locations/us-west4/repositories/test-repo/workspaces/test-workspace", + "endpoint": ( + "https://geminidataanalytics.googleapis.com/v1/a2a/" + "projects/test/locations/us-west4/agents/" + "dataengineeringagent" + ), + "target_workspace": ( + "projects/test/locations/us-west4/repositories/" + "test-repo/workspaces/test-workspace" + ), } # Mock google.auth.default during initialization @@ -58,7 +65,11 @@ def test_generator_setup_missing_endpoint(): def test_generator_setup_missing_workspace(): config = { "generator": "data_engineering_agent", - "endpoint": "https://geminidataanalytics.googleapis.com/v1/a2a/projects/test/locations/us-west4/agents/dataengineeringagent", + "endpoint": ( + "https://geminidataanalytics.googleapis.com/v1/a2a/" + "projects/test/locations/us-west4/agents/" + "dataengineeringagent" + ), } with pytest.raises(ValueError) as excinfo: DataEngineeringAgentGenerator(config) @@ -68,7 +79,9 @@ def test_generator_setup_missing_workspace(): @pytest.mark.anyio @patch("google.auth.default") async def test_get_credentials_error_resiliency_default(mock_auth_default): - mock_auth_default.side_effect = DefaultCredentialsError("Credentials missing.") + mock_auth_default.side_effect = DefaultCredentialsError( + "Credentials missing." + ) service = GcpAdcCredentialService() with pytest.raises(DefaultCredentialsError): @@ -87,5 +100,3 @@ async def test_get_credentials_error_resiliency_refresh(mock_auth_default): with pytest.raises(RefreshError): await service.get_credentials("oauth", None) - - From 0e84bc5f1005804f3307f4e28c2acc8cd3f39d4d Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Fri, 29 May 2026 00:25:36 +0000 Subject: [PATCH 09/10] feat(dea): make GCP ADC credential retrieval thread-safe and non-blocking - Configure `GcpAdcCredentialService` using `asyncio.Lock` to ensure concurrency safety across parallel requests. - Move gcloud auth default credentials initialization off the main event loop using `asyncio.to_thread` to prevent blocking. - Clean up unused subprocess import in `data_engineering_agent.py`. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- .../models/data_engineering_agent.py | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index cab50512..7357c34c 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -1,6 +1,5 @@ import asyncio import logging -import subprocess from typing import Any from a2a.client import ClientCallContext @@ -15,35 +14,46 @@ class GcpAdcCredentialService(CredentialService): """GCP Application Default Credentials (ADC) service for A2A SDK. - This provider intentionally only services OAuth/OAuth2 schemes. + This provider is concurrency-safe, non-blocking, and dynamically caches + tokens to prevent redundant refreshes on the hot path. It intentionally + only services OAuth/OAuth2 schemes. """ def __init__(self): self.logger = logging.getLogger(__name__) self.credentials = None + self._lock = None async def get_credentials( self, security_scheme_name: str, context: ClientCallContext | None, - ) -> str | None: + ) -> str: if security_scheme_name.lower() not in ("oauth", "oauth2"): raise ValueError( f"GcpAdcCredentialService only services 'oauth' or 'oauth2' " f"schemes, got '{security_scheme_name}'" ) - try: - if self.credentials is None: - self.credentials, _ = google.auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform"] - ) - - if not self.credentials.valid: - await asyncio.to_thread(self.credentials.refresh, Request()) + if self._lock is None: + self._lock = asyncio.Lock() - self.logger.debug("Retrieved GCP ADC token successfully.") - return self.credentials.token + try: + async with self._lock: + if self.credentials is None: + credentials, _ = await asyncio.to_thread( + google.auth.default, + scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) + self.credentials = credentials + + if not self.credentials.valid: + await asyncio.to_thread( + self.credentials.refresh, Request() + ) + + self.logger.debug("Retrieved GCP ADC token successfully.") + return self.credentials.token except (DefaultCredentialsError, RefreshError) as e: self.logger.error( @@ -89,7 +99,7 @@ def __init__(self, querygenerator_config: dict[str, Any]): "GcpAdcCredentialService." ) - def generate_internal(self, prompt: str) -> subprocess.CompletedProcess: + def generate_internal(self, prompt: str) -> Any: """Stubbed messaging logic for WIP scaffolding (Task 1.3).""" raise NotImplementedError( "Task 1.3 DEA A2A messaging logic in generate_internal is " From be0d31926b6b34eb8ae3313b5e729176dbbac9a0 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Fri, 29 May 2026 15:38:31 +0000 Subject: [PATCH 10/10] style(dea): simplify GcpAdcCredentialService docstring in generators - Clean up and shorten the docstring of `GcpAdcCredentialService` to remove unnecessary details and resolve E501 line-length warnings. TAG=agy CONV=aa927cc7-418a-41e3-b658-9b82915e18eb --- evalbench/generators/models/data_engineering_agent.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evalbench/generators/models/data_engineering_agent.py b/evalbench/generators/models/data_engineering_agent.py index 7357c34c..ebcc98f6 100644 --- a/evalbench/generators/models/data_engineering_agent.py +++ b/evalbench/generators/models/data_engineering_agent.py @@ -14,9 +14,7 @@ class GcpAdcCredentialService(CredentialService): """GCP Application Default Credentials (ADC) service for A2A SDK. - This provider is concurrency-safe, non-blocking, and dynamically caches - tokens to prevent redundant refreshes on the hot path. It intentionally - only services OAuth/OAuth2 schemes. + This provider only services OAuth/OAuth2 schemes. """ def __init__(self): @@ -43,7 +41,9 @@ async def get_credentials( if self.credentials is None: credentials, _ = await asyncio.to_thread( google.auth.default, - scopes=["https://www.googleapis.com/auth/cloud-platform"] + scopes=[ + "https://www.googleapis.com/auth/cloud-platform" + ] ) self.credentials = credentials