diff options
author | Alexander Smirnov <alex@ydb.tech> | 2025-05-02 21:33:53 +0000 |
---|---|---|
committer | Alexander Smirnov <alex@ydb.tech> | 2025-05-02 21:33:53 +0000 |
commit | 726e4fe93a06affb8a5805f80f779e1ebc891ffc (patch) | |
tree | 0a22ac4b5a192f4cfc89252997f3d555396954d2 /contrib/python | |
parent | cfede7fd10c5032b322bc335caff4d30c7674e6f (diff) | |
parent | 940be57633df4940e96f5754ce1bc0d4e5934dc8 (diff) | |
download | ydb-726e4fe93a06affb8a5805f80f779e1ebc891ffc.tar.gz |
Merge pull request #17944 from ydb-platform/merge-libs-250501-0050
Diffstat (limited to 'contrib/python')
30 files changed, 853 insertions, 79 deletions
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA index 952e020e3cd..53da88bdec8 100644 --- a/contrib/python/google-auth/py3/.dist-info/METADATA +++ b/contrib/python/google-auth/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-auth -Version: 2.38.0 +Version: 2.39.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform @@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License @@ -28,21 +29,49 @@ Requires-Dist: cachetools<6.0,>=2.0.0 Requires-Dist: pyasn1-modules>=0.2.1 Requires-Dist: rsa<5,>=3.1.4 Provides-Extra: aiohttp -Requires-Dist: aiohttp<4.0.0.dev0,>=3.6.2; extra == "aiohttp" -Requires-Dist: requests<3.0.0.dev0,>=2.20.0; extra == "aiohttp" +Requires-Dist: aiohttp<4.0.0,>=3.6.2; extra == "aiohttp" +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "aiohttp" Provides-Extra: enterprise_cert Requires-Dist: cryptography; extra == "enterprise-cert" Requires-Dist: pyopenssl; extra == "enterprise-cert" Provides-Extra: pyjwt Requires-Dist: pyjwt>=2.0; extra == "pyjwt" Requires-Dist: cryptography>=38.0.3; extra == "pyjwt" +Requires-Dist: cryptography<39.0.0; python_version < "3.8" and extra == "pyjwt" Provides-Extra: pyopenssl Requires-Dist: pyopenssl>=20.0.0; extra == "pyopenssl" Requires-Dist: cryptography>=38.0.3; extra == "pyopenssl" +Requires-Dist: cryptography<39.0.0; python_version < "3.8" and extra == "pyopenssl" Provides-Extra: reauth Requires-Dist: pyu2f>=0.1.5; extra == "reauth" Provides-Extra: requests -Requires-Dist: requests<3.0.0.dev0,>=2.20.0; extra == "requests" +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "requests" +Provides-Extra: testing +Requires-Dist: grpcio; extra == "testing" +Requires-Dist: flask; extra == "testing" +Requires-Dist: freezegun; extra == "testing" +Requires-Dist: mock; extra == "testing" +Requires-Dist: oauth2client; extra == "testing" +Requires-Dist: pyjwt>=2.0; extra == "testing" +Requires-Dist: cryptography>=38.0.3; extra == "testing" +Requires-Dist: pytest; extra == "testing" +Requires-Dist: pytest-cov; extra == "testing" +Requires-Dist: pytest-localserver; extra == "testing" +Requires-Dist: pyopenssl>=20.0.0; extra == "testing" +Requires-Dist: pyu2f>=0.1.5; extra == "testing" +Requires-Dist: responses; extra == "testing" +Requires-Dist: urllib3; extra == "testing" +Requires-Dist: packaging; extra == "testing" +Requires-Dist: aiohttp<4.0.0,>=3.6.2; extra == "testing" +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "testing" +Requires-Dist: aioresponses; extra == "testing" +Requires-Dist: pytest-asyncio; extra == "testing" +Requires-Dist: pyopenssl<24.3.0; extra == "testing" +Requires-Dist: aiohttp<3.10.0; extra == "testing" +Requires-Dist: cryptography<39.0.0; python_version < "3.8" and extra == "testing" +Provides-Extra: urllib3 +Requires-Dist: urllib3; extra == "urllib3" +Requires-Dist: packaging; extra == "urllib3" Google Auth Python Library ========================== diff --git a/contrib/python/google-auth/py3/google/auth/_default.py b/contrib/python/google-auth/py3/google/auth/_default.py index 1234fb25d78..cf0cdd77298 100644 --- a/contrib/python/google-auth/py3/google/auth/_default.py +++ b/contrib/python/google-auth/py3/google/auth/_default.py @@ -484,42 +484,8 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): from google.auth import impersonated_credentials try: - source_credentials_info = info.get("source_credentials") - source_credentials_type = source_credentials_info.get("type") - if source_credentials_type == _AUTHORIZED_USER_TYPE: - source_credentials, _ = _get_authorized_user_credentials( - filename, source_credentials_info - ) - elif source_credentials_type == _SERVICE_ACCOUNT_TYPE: - source_credentials, _ = _get_service_account_credentials( - filename, source_credentials_info - ) - elif source_credentials_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: - source_credentials, _ = _get_external_account_authorized_user_credentials( - filename, source_credentials_info - ) - else: - raise exceptions.InvalidType( - "source credential of type {} is not supported.".format( - source_credentials_type - ) - ) - impersonation_url = info.get("service_account_impersonation_url") - start_index = impersonation_url.rfind("/") - end_index = impersonation_url.find(":generateAccessToken") - if start_index == -1 or end_index == -1 or start_index > end_index: - raise exceptions.InvalidValue( - "Cannot extract target principal from {}".format(impersonation_url) - ) - target_principal = impersonation_url[start_index + 1 : end_index] - delegates = info.get("delegates") - quota_project_id = info.get("quota_project_id") - credentials = impersonated_credentials.Credentials( - source_credentials, - target_principal, - scopes, - delegates, - quota_project_id=quota_project_id, + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes ) except ValueError as caught_exc: msg = "Failed to load impersonated service account credentials from {}".format( diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py index 06f99de0e2c..ddbe8ac2f70 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py @@ -159,6 +159,7 @@ def get( retry_count=5, headers=None, return_none_for_not_found_error=False, + timeout=_METADATA_DEFAULT_TIMEOUT, ): """Fetch a resource from the metadata server. @@ -178,6 +179,7 @@ def get( headers (Optional[Mapping[str, str]]): Headers for the request. return_none_for_not_found_error (Optional[bool]): If True, returns None for 404 error instead of throwing an exception. + timeout (int): How long to wait, in seconds for the metadata server to respond. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -204,7 +206,9 @@ def get( failure_reason = None for attempt in backoff: try: - response = request(url=url, method="GET", headers=headers_to_use) + response = request( + url=url, method="GET", headers=headers_to_use, timeout=timeout + ) if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES: _LOGGER.warning( "Compute Engine Metadata server unavailable on " diff --git a/contrib/python/google-auth/py3/google/auth/identity_pool.py b/contrib/python/google-auth/py3/google/auth/identity_pool.py index 47f9a55715c..c06f8842870 100644 --- a/contrib/python/google-auth/py3/google/auth/identity_pool.py +++ b/contrib/python/google-auth/py3/google/auth/identity_pool.py @@ -41,6 +41,7 @@ try: except ImportError: # pragma: NO COVER from collections import Mapping # type: ignore import abc +import base64 import json import os from typing import NamedTuple @@ -145,9 +146,88 @@ class _UrlSupplier(SubjectTokenSupplier): class _X509Supplier(SubjectTokenSupplier): """Internal supplier for X509 workload credentials. This class is used internally and always returns an empty string as the subject token.""" + def __init__(self, trust_chain_path, leaf_cert_callback): + self._trust_chain_path = trust_chain_path + self._leaf_cert_callback = leaf_cert_callback + @_helpers.copy_docstring(SubjectTokenSupplier) def get_subject_token(self, context, request): - return "" + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + leaf_cert = crypto.load_certificate( + crypto.FILETYPE_PEM, self._leaf_cert_callback() + ) + trust_chain = self._read_trust_chain() + cert_chain = [] + + cert_chain.append(_X509Supplier._encode_cert(leaf_cert)) + + if trust_chain is None or len(trust_chain) == 0: + return json.dumps(cert_chain) + + # Append the first cert if it is not the leaf cert. + first_cert = _X509Supplier._encode_cert(trust_chain[0]) + if first_cert != cert_chain[0]: + cert_chain.append(first_cert) + + for i in range(1, len(trust_chain)): + encoded = _X509Supplier._encode_cert(trust_chain[i]) + # Check if the current cert is the leaf cert and raise an exception if it is. + if encoded == cert_chain[0]: + raise exceptions.RefreshError( + "The leaf certificate must be at the top of the trust chain file" + ) + else: + cert_chain.append(encoded) + return json.dumps(cert_chain) + + def _read_trust_chain(self): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + certificate_trust_chain = [] + # If no trust chain path was provided, return an empty list. + if self._trust_chain_path is None or self._trust_chain_path == "": + return certificate_trust_chain + try: + # Open the trust chain file. + with open(self._trust_chain_path, "rb") as f: + trust_chain_data = f.read() + # Split PEM data into individual certificates. + cert_blocks = trust_chain_data.split(b"-----BEGIN CERTIFICATE-----") + for cert_block in cert_blocks: + # Skip empty blocks. + if cert_block.strip(): + cert_data = b"-----BEGIN CERTIFICATE-----" + cert_block + try: + # Load each certificate and add it to the trust chain. + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, cert_data + ) + certificate_trust_chain.append(cert) + except Exception as e: + raise exceptions.RefreshError( + "Error loading PEM certificates from the trust chain file '{}'".format( + self._trust_chain_path + ) + ) from e + return certificate_trust_chain + except FileNotFoundError: + raise exceptions.RefreshError( + "Trust chain file '{}' was not found.".format(self._trust_chain_path) + ) + + def _encode_cert(cert): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + return base64.b64encode( + crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) + ).decode("utf-8") def _parse_token_data(token_content, format_type="text", subject_token_field_name=None): @@ -296,7 +376,9 @@ class Credentials(external_account.Credentials): self._credential_source_headers, ) else: # self._credential_source_certificate - self._subject_token_supplier = _X509Supplier() + self._subject_token_supplier = _X509Supplier( + self._trust_chain_path, self._get_cert_bytes + ) @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): @@ -314,6 +396,10 @@ class Credentials(external_account.Credentials): self._certificate_config_location ) + def _get_cert_bytes(self): + cert_path, _ = self._get_mtls_cert_and_key_paths() + return _mtls_helper._read_cert_file(cert_path) + def _mtls_required(self): return self._credential_source_certificate is not None @@ -350,6 +436,9 @@ class Credentials(external_account.Credentials): use_default = self._credential_source_certificate.get( "use_default_certificate_config" ) + self._trust_chain_path = self._credential_source_certificate.get( + "trust_chain_path" + ) if self._certificate_config_location and use_default: raise exceptions.MalformedError( "Invalid certificate configuration, certificate_config_location cannot be specified when use_default_certificate_config = true." diff --git a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py index ed7e3f00b1c..d49998cfbdc 100644 --- a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py +++ b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py @@ -47,6 +47,12 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE = "authorized_user" +_SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE = "service_account" +_SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = ( + "external_account_authorized_user" +) + def _make_iam_token_request( request, @@ -410,6 +416,75 @@ class Credentials( cred._target_scopes = scopes or default_scopes return cred + @classmethod + def from_impersonated_service_account_info(cls, info, scopes=None): + """Creates a Credentials instance from parsed impersonated service account credentials info. + + Args: + info (Mapping[str, str]): The impersonated service account credentials info in Google + format. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + InvalidType: If the info["source_credentials"] are not a supported impersonation type + InvalidValue: If the info["service_account_impersonation_url"] is not in the expected format. + ValueError: If the info is not in the expected format. + """ + + source_credentials_info = info.get("source_credentials") + source_credentials_type = source_credentials_info.get("type") + if source_credentials_type == _SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE: + from google.oauth2 import credentials + + source_credentials = credentials.Credentials.from_authorized_user_info( + source_credentials_info + ) + elif source_credentials_type == _SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE: + from google.oauth2 import service_account + + source_credentials = service_account.Credentials.from_service_account_info( + source_credentials_info + ) + elif ( + source_credentials_type + == _SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE + ): + from google.auth import external_account_authorized_user + + source_credentials = external_account_authorized_user.Credentials.from_info( + source_credentials_info + ) + else: + raise exceptions.InvalidType( + "source credential of type {} is not supported.".format( + source_credentials_type + ) + ) + + impersonation_url = info.get("service_account_impersonation_url") + start_index = impersonation_url.rfind("/") + end_index = impersonation_url.find(":generateAccessToken") + if start_index == -1 or end_index == -1 or start_index > end_index: + raise exceptions.InvalidValue( + "Cannot extract target principal from {}".format(impersonation_url) + ) + target_principal = impersonation_url[start_index + 1 : end_index] + delegates = info.get("delegates") + quota_project_id = info.get("quota_project_id") + + return cls( + source_credentials, + target_principal, + scopes, + delegates, + quota_project_id=quota_project_id, + ) + class IDTokenCredentials(credentials.CredentialsWithQuotaProject): """Open ID Connect ID Token-based service account credentials. diff --git a/contrib/python/google-auth/py3/google/auth/transport/urllib3.py b/contrib/python/google-auth/py3/google/auth/transport/urllib3.py index 63144f5fffa..db4fa93ff11 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/urllib3.py +++ b/contrib/python/google-auth/py3/google/auth/transport/urllib3.py @@ -34,13 +34,21 @@ except ImportError: # pragma: NO COVER try: import urllib3 # type: ignore import urllib3.exceptions # type: ignore + from packaging import version # type: ignore except ImportError as caught_exc: # pragma: NO COVER raise ImportError( - "The urllib3 library is not installed from please install the " - "urllib3 package to use the urllib3 transport." + "" + f"Error: {caught_exc}." + " The 'google-auth' library requires the extras installed " + "for urllib3 network transport." + "\n" + "Please install the necessary dependencies using pip:\n" + " pip install google-auth[urllib3]\n" + "\n" + "(Note: Using '[urllib3]' ensures the specific dependencies needed for this feature are installed. " + "We recommend running this command in your virtual environment.)" ) from caught_exc -from packaging import version # type: ignore from google.auth import environment_vars from google.auth import exceptions @@ -414,7 +422,7 @@ class AuthorizedHttp(RequestMethods): # type: ignore body=body, headers=headers, _credential_refresh_attempt=_credential_refresh_attempt + 1, - **kwargs + **kwargs, ) return response diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py index 41a80e6c676..393caa8ad44 100644 --- a/contrib/python/google-auth/py3/google/auth/version.py +++ b/contrib/python/google-auth/py3/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.38.0" +__version__ = "2.39.0" diff --git a/contrib/python/google-auth/py3/google/oauth2/id_token.py b/contrib/python/google-auth/py3/google/oauth2/id_token.py index b68ab6b303a..a6c51ce6381 100644 --- a/contrib/python/google-auth/py3/google/oauth2/id_token.py +++ b/contrib/python/google-auth/py3/google/oauth2/id_token.py @@ -284,6 +284,18 @@ def fetch_id_token_credentials(audience, request=None): return service_account.IDTokenCredentials.from_service_account_info( info, target_audience=audience ) + elif info.get("type") == "impersonated_service_account": + from google.auth import impersonated_credentials + + target_credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + + return impersonated_credentials.IDTokenCredentials( + target_credentials=target_credentials, + target_audience=audience, + include_email=True, + ) except ValueError as caught_exc: new_exc = exceptions.DefaultCredentialsError( "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", diff --git a/contrib/python/google-auth/py3/patches/01-fix-tests.patch b/contrib/python/google-auth/py3/patches/01-fix-tests.patch index 1065289c64b..0494eab3354 100644 --- a/contrib/python/google-auth/py3/patches/01-fix-tests.patch +++ b/contrib/python/google-auth/py3/patches/01-fix-tests.patch @@ -47,7 +47,7 @@ +DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "..", "data") --- contrib/python/google-auth/py3/tests/oauth2/test_id_token.py (index) +++ contrib/python/google-auth/py3/tests/oauth2/test_id_token.py (working tree) -@@ -24,8 +24,9 @@ import google.auth.compute_engine._metadata +@@ -24,12 +24,13 @@ import google.auth.compute_engine._metadata from google.oauth2 import id_token from google.oauth2 import service_account @@ -56,7 +56,12 @@ - os.path.dirname(__file__), "../data/service_account.json" + os.path.dirname(yc.source_path(__file__)), "../data/service_account.json" ) - ID_TOKEN_AUDIENCE = "https://pubsub.googleapis.com" + + IMPERSONATED_SERVICE_ACCOUNT_FILE = os.path.join( +- os.path.dirname(__file__), ++ os.path.dirname(yc.source_path(__file__)), + "../data/impersonated_service_account_authorized_user_source.json", + ) @@ -265,1 +266,1 @@ def test_fetch_id_token_no_cred_exists(monkeypatch): - os.path.dirname(__file__), "../data/authorized_user.json" diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py index a768b17fa0d..98d08fe4505 100644 --- a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py +++ b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py @@ -176,6 +176,7 @@ def test_get_success_json(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result[key] == value @@ -194,6 +195,7 @@ def test_get_success_json_content_type_charset(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result[key] == value @@ -213,6 +215,7 @@ def test_get_success_retry(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 2 assert result[key] == value @@ -228,6 +231,7 @@ def test_get_success_text(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -243,6 +247,7 @@ def test_get_success_params(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -257,6 +262,7 @@ def test_get_success_recursive_and_params(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -271,6 +277,7 @@ def test_get_success_recursive(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -292,6 +299,7 @@ def _test_get_success_custom_root_new_variable(): method="GET", url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -312,6 +320,7 @@ def _test_get_success_custom_root_old_variable(): method="GET", url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -328,6 +337,7 @@ def test_get_failure(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -340,6 +350,7 @@ def test_get_return_none_for_not_found_error(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -359,6 +370,7 @@ def test_get_failure_connection_failed(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -377,6 +389,7 @@ def test_get_too_many_requests_retryable_error_failure(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -393,6 +406,7 @@ def test_get_failure_bad_json(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -406,6 +420,7 @@ def test_get_project_id(): method="GET", url=_metadata._METADATA_ROOT + "project/project-id", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert project_id == project @@ -421,6 +436,7 @@ def test_get_universe_domain_success(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "fake_universe_domain" @@ -434,6 +450,7 @@ def test_get_universe_domain_success_empty_response(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "googleapis.com" @@ -449,6 +466,7 @@ def test_get_universe_domain_not_found(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "googleapis.com" @@ -469,6 +487,7 @@ def test_get_universe_domain_retryable_error_failure(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -511,11 +530,13 @@ def test_get_universe_domain_retryable_error_success(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) request_ok.assert_called_once_with( method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "fake_universe_domain" @@ -535,6 +556,7 @@ def test_get_universe_domain_other_error(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -559,6 +581,7 @@ def test_get_service_account_token(utcnow, mock_metrics_header_value): "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -585,6 +608,7 @@ def test_get_service_account_token_with_scopes_list(utcnow, mock_metrics_header_ "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -613,6 +637,7 @@ def test_get_service_account_token_with_scopes_string( "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -630,6 +655,7 @@ def test_get_service_account_info(): method="GET", url=_metadata._METADATA_ROOT + PATH + "/?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert info[key] == value diff --git a/contrib/python/google-auth/py3/tests/data/trust_chain_with_leaf.pem b/contrib/python/google-auth/py3/tests/data/trust_chain_with_leaf.pem new file mode 100644 index 00000000000..250387d9d59 --- /dev/null +++ b/contrib/python/google-auth/py3/tests/data/trust_chain_with_leaf.pem @@ -0,0 +1,52 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV +BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV +MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM +7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer +uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp +gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4 ++WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3 +ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O +gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh +GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr +odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk ++JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9 +ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql +ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT +cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/contrib/python/google-auth/py3/tests/data/trust_chain_without_leaf.pem b/contrib/python/google-auth/py3/tests/data/trust_chain_without_leaf.pem new file mode 100644 index 00000000000..9da0f37fedf --- /dev/null +++ b/contrib/python/google-auth/py3/tests/data/trust_chain_without_leaf.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/contrib/python/google-auth/py3/tests/data/trust_chain_wrong_order.pem b/contrib/python/google-auth/py3/tests/data/trust_chain_wrong_order.pem new file mode 100644 index 00000000000..e8dc5d35931 --- /dev/null +++ b/contrib/python/google-auth/py3/tests/data/trust_chain_wrong_order.pem @@ -0,0 +1,52 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV +BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV +MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM +7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer +uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp +gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4 ++WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3 +ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O +gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh +GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr +odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk ++JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9 +ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql +ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT +cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_id_token.py b/contrib/python/google-auth/py3/tests/oauth2/test_id_token.py index 65189df128c..5dc125fb566 100644 --- a/contrib/python/google-auth/py3/tests/oauth2/test_id_token.py +++ b/contrib/python/google-auth/py3/tests/oauth2/test_id_token.py @@ -20,6 +20,7 @@ import pytest # type: ignore from google.auth import environment_vars from google.auth import exceptions +from google.auth import impersonated_credentials from google.auth import transport from google.oauth2 import id_token from google.oauth2 import service_account @@ -28,6 +29,12 @@ import yatest.common as yc SERVICE_ACCOUNT_FILE = os.path.join( os.path.dirname(yc.source_path(__file__)), "../data/service_account.json" ) + +IMPERSONATED_SERVICE_ACCOUNT_FILE = os.path.join( + os.path.dirname(yc.source_path(__file__)), + "../data/impersonated_service_account_authorized_user_source.json", +) + ID_TOKEN_AUDIENCE = "https://pubsub.googleapis.com" @@ -263,6 +270,14 @@ def test_fetch_id_token_credentials_from_explicit_cred_json_file(monkeypatch): assert cred._target_audience == ID_TOKEN_AUDIENCE +def test_fetch_id_token_credentials_from_impersonated_cred_json_file(monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, IMPERSONATED_SERVICE_ACCOUNT_FILE) + + cred = id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) + assert isinstance(cred, impersonated_credentials.IDTokenCredentials) + assert cred._target_audience == ID_TOKEN_AUDIENCE + + def test_fetch_id_token_credentials_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) diff --git a/contrib/python/google-auth/py3/tests/test__oauth2client.py b/contrib/python/google-auth/py3/tests/test__oauth2client.py index 1db595fd9ac..61eaf17c2de 100644 --- a/contrib/python/google-auth/py3/tests/test__oauth2client.py +++ b/contrib/python/google-auth/py3/tests/test__oauth2client.py @@ -117,6 +117,14 @@ def _test__convert_appengine_app_assertion_credentials( app_identity, mock_oauth2client_gae_imports ): + # `oauth2client` requires `cgi` which was removed in Python 3.13 + # See https://github.com/googleapis/oauth2client/blob/50d20532a748f18e53f7d24ccbe6647132c979a9/oauth2client/contrib/appengine.py#L20 + # oauth2client is no longer being updated so this test must be skipped on newer Python Runtimes + if sys.version_info >= (3, 13): # pragma: NO COVER + pytest.skip( + "Skipping test for Python 3.13+ due to oauth2client incompatibility." + ) + import oauth2client.contrib.appengine # type: ignore service_account_id = "service_account_id" @@ -166,6 +174,14 @@ def reset__oauth2client_module(): def _test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): + # `oauth2client` requires `cgi` which was removed in Python 3.13 + # See https://github.com/googleapis/oauth2client/blob/50d20532a748f18e53f7d24ccbe6647132c979a9/oauth2client/contrib/appengine.py#L20 + # oauth2client is no longer being updated so this test must be skipped on newer Python Runtimes + if sys.version_info >= (3, 13): # pragma: NO COVER + pytest.skip( + "Skipping test for Python 3.13+ due to oauth2client incompatibility." + ) + importlib.reload(_oauth2client) assert _oauth2client._HAS_APPENGINE diff --git a/contrib/python/google-auth/py3/tests/test_identity_pool.py b/contrib/python/google-auth/py3/tests/test_identity_pool.py index cc6cbf08827..4d78a5c22ea 100644 --- a/contrib/python/google-auth/py3/tests/test_identity_pool.py +++ b/contrib/python/google-auth/py3/tests/test_identity_pool.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import datetime import http.client as http_client import json @@ -19,6 +20,7 @@ import os import urllib import mock +from OpenSSL import crypto import pytest # type: ignore from google.auth import _helpers, external_account @@ -49,6 +51,13 @@ import yatest.common as yc DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "data") SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt") SUBJECT_TOKEN_JSON_FILE = os.path.join(DATA_DIR, "external_subject_token.json") +TRUST_CHAIN_WITH_LEAF_FILE = os.path.join(DATA_DIR, "trust_chain_with_leaf.pem") +TRUST_CHAIN_WITHOUT_LEAF_FILE = os.path.join(DATA_DIR, "trust_chain_without_leaf.pem") +TRUST_CHAIN_WRONG_ORDER_FILE = os.path.join(DATA_DIR, "trust_chain_wrong_order.pem") +CERT_FILE = os.path.join(DATA_DIR, "public_cert.pem") +KEY_FILE = os.path.join(DATA_DIR, "privatekey.pem") +OTHER_CERT_FILE = os.path.join(DATA_DIR, "other_cert.pem") + SUBJECT_TOKEN_FIELD_NAME = "access_token" with open(SUBJECT_TOKEN_TEXT_FILE) as fh: @@ -58,6 +67,20 @@ with open(SUBJECT_TOKEN_JSON_FILE) as fh: JSON_FILE_CONTENT = json.load(fh) JSON_FILE_SUBJECT_TOKEN = JSON_FILE_CONTENT.get(SUBJECT_TOKEN_FIELD_NAME) +with open(CERT_FILE, "rb") as f: + CERT_FILE_CONTENT = base64.b64encode( + crypto.dump_certificate( + crypto.FILETYPE_ASN1, crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + ) + ).decode("utf-8") + +with open(OTHER_CERT_FILE, "rb") as f: + OTHER_CERT_FILE_CONTENT = base64.b64encode( + crypto.dump_certificate( + crypto.FILETYPE_ASN1, crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + ) + ).decode("utf-8") + TOKEN_URL = "https://sts.googleapis.com/v1/token" TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" @@ -186,6 +209,24 @@ class TestCredentials(object): CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT = { "certificate": {"certificate_config_location": "path/to/config"} } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITH_LEAF = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WITH_LEAF_FILE, + } + } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITHOUT_LEAF = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WITHOUT_LEAF_FILE, + } + } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WRONG_ORDER = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WRONG_ORDER_FILE, + } + } SUCCESS_RESPONSE = { "access_token": "ACCESS_TOKEN", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", @@ -937,14 +978,126 @@ class TestCredentials(object): assert subject_token == JSON_FILE_SUBJECT_TOKEN - def test_retrieve_subject_token_certificate(self): + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_default( + self, mock_get_workload_cert_and_key_paths + ): credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE ) subject_token = credentials.retrieve_subject_token(None) - assert subject_token == "" + assert subject_token == json.dumps([CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_non_default_path( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == json.dumps([CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_with_leaf( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITH_LEAF + ) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == json.dumps([CERT_FILE_CONTENT, OTHER_CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_without_leaf( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITHOUT_LEAF + ) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == json.dumps([CERT_FILE_CONTENT, OTHER_CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_invalid_order( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WRONG_ORDER + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "The leaf certificate must be at the top of the trust chain file" + ) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_file_does_not_exist( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source={ + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": "fake.pem", + } + } + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match("Trust chain file 'fake.pem' was not found.") + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_invalid_trust_chain_file( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source={ + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": SUBJECT_TOKEN_TEXT_FILE, + } + } + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match("Error loading PEM certificates from the trust chain file") def test_retrieve_subject_token_json_file_invalid_field_name(self): credential_source = { diff --git a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py index 0321a1a1d7b..9aeb505fdd9 100644 --- a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py +++ b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import datetime import http.client as http_client import json @@ -36,6 +37,9 @@ with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") +IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( + DATA_DIR, "impersonated_service_account_authorized_user_source.json" +) ID_TOKEN_DATA = ( "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew" @@ -50,6 +54,9 @@ ID_TOKEN_EXPIRY = 1564475051 with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) +with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE, "rb") as fh: + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO = json.load(fh) + SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") TOKEN_URI = "https://example.com/oauth2/token" @@ -149,6 +156,38 @@ class TestImpersonatedCredentials(object): iam_endpoint_override=iam_endpoint_override, ) + def test_from_impersonated_service_account_info(self): + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + + def test_from_impersonated_service_account_info_with_invalid_source_credentials_type( + self + ): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + assert "source_credentials" in info + # Set the source_credentials to an invalid type + info["source_credentials"]["type"] = "invalid_type" + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert excinfo.match( + "source credential of type {} is not supported".format("invalid_type") + ) + + def test_from_impersonated_service_account_info_with_invalid_impersonation_url( + self + ): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + info["service_account_impersonation_url"] = "invalid_url" + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert excinfo.match(r"Cannot extract target principal from") + def test_get_cred_info(self): credentials = self.make_credentials() assert not credentials.get_cred_info() diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make index bb762919dc6..eddecb079e5 100644 --- a/contrib/python/google-auth/py3/ya.make +++ b/contrib/python/google-auth/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.38.0) +VERSION(2.39.0) LICENSE(Apache-2.0) diff --git a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA b/contrib/python/prompt-toolkit/py3/.dist-info/METADATA index 8d4f5d343d2..265fb8183bd 100644 --- a/contrib/python/prompt-toolkit/py3/.dist-info/METADATA +++ b/contrib/python/prompt-toolkit/py3/.dist-info/METADATA @@ -1,8 +1,7 @@ -Metadata-Version: 2.2 +Metadata-Version: 2.4 Name: prompt_toolkit -Version: 3.0.50 +Version: 3.0.51 Summary: Library for building powerful interactive command lines in Python -Home-page: https://github.com/prompt-toolkit/python-prompt-toolkit Author: Jonathan Slenders Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers @@ -18,19 +17,12 @@ Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python Classifier: Topic :: Software Development -Requires-Python: >=3.8.0 +Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE License-File: AUTHORS.rst Requires-Dist: wcwidth -Dynamic: author -Dynamic: classifier -Dynamic: description -Dynamic: description-content-type -Dynamic: home-page -Dynamic: requires-dist -Dynamic: requires-python -Dynamic: summary +Dynamic: license-file Python Prompt Toolkit ===================== diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py index 94727e7cb22..ebaa57dc81b 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/__init__.py @@ -17,6 +17,7 @@ Probably, to get started, you might also want to have a look at from __future__ import annotations import re +from importlib import metadata # note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number pep440 = re.compile( @@ -28,7 +29,7 @@ from .formatted_text import ANSI, HTML from .shortcuts import PromptSession, print_formatted_text, prompt # Don't forget to update in `docs/conf.py`! -__version__ = "3.0.50" +__version__ = metadata.version("prompt_toolkit") assert pep440.match(__version__) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py index 8569bd2cff7..b72b69ee212 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/completion/nested.py @@ -69,7 +69,7 @@ class NestedCompleter(Completer): elif isinstance(value, dict): options[key] = cls.from_nested_dict(value) elif isinstance(value, set): - options[key] = cls.from_nested_dict({item: None for item in value}) + options[key] = cls.from_nested_dict(dict.fromkeys(value)) else: assert value is None options[key] = None diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py index 43228c3cda1..a6f78cb4e06 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py @@ -89,8 +89,7 @@ def split_lines( parts = string.split("\n") for part in parts[:-1]: - if part: - line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) + line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) yield line line = [] diff --git a/contrib/python/prompt-toolkit/py3/tests/test_filter.py b/contrib/python/prompt-toolkit/py3/tests/test_filter.py index f7184c286f2..70ddd5cdf59 100644 --- a/contrib/python/prompt-toolkit/py3/tests/test_filter.py +++ b/contrib/python/prompt-toolkit/py3/tests/test_filter.py @@ -16,7 +16,7 @@ def test_always(): def test_invert(): assert not (~Always())() - assert ~Never()() + assert (~Never())() c = ~Condition(lambda: False) assert c() diff --git a/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py b/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py index 843aac16191..60f9cdf459b 100644 --- a/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py +++ b/contrib/python/prompt-toolkit/py3/tests/test_formatted_text.py @@ -274,7 +274,7 @@ def test_split_lines_3(): lines = list(split_lines([("class:a", "\n")])) assert lines == [ - [], + [("class:a", "")], [("class:a", "")], ] @@ -284,3 +284,15 @@ def test_split_lines_3(): assert lines == [ [("class:a", "")], ] + + +def test_split_lines_4(): + "Edge cases: inputs starting and ending with newlines." + # -1- + lines = list(split_lines([("class:a", "\nline1\n")])) + + assert lines == [ + [("class:a", "")], + [("class:a", "line1")], + [("class:a", "")], + ] diff --git a/contrib/python/prompt-toolkit/py3/ya.make b/contrib/python/prompt-toolkit/py3/ya.make index 5eed9c2519b..fd9a72b9e6e 100644 --- a/contrib/python/prompt-toolkit/py3/ya.make +++ b/contrib/python/prompt-toolkit/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(3.0.50) +VERSION(3.0.51) LICENSE(BSD-3-Clause) diff --git a/contrib/python/pythran/bin/pythran/ya.make b/contrib/python/pythran/bin/pythran/ya.make index 7a4a0b9fd6f..370f7347bd6 100644 --- a/contrib/python/pythran/bin/pythran/ya.make +++ b/contrib/python/pythran/bin/pythran/ya.make @@ -10,4 +10,119 @@ PEERDIR( PY_MAIN(pythran.run:run) +INDUCED_DEPS(h+cpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/assert.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/getattr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/int_.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/max.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/range.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/builtins/tuple.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/core.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/assert.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/getattr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/int_.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/max.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/min.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/range.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/builtins/tuple.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/numpy/empty_like.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/numpy/float64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/numpy/square.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/add.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/div.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/eq.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/floordiv.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/gt.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/iadd.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/le.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/lt.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/mod.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/mul.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/neg.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/operator_/sub.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/float.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/float64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/int.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/ndarray.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/numpy_texpr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/include/types/str.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/numpy/empty_like.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/numpy/float64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/numpy/square.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/add.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/div.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/eq.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/floordiv.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/gt.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/iadd.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/le.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/lt.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/mod.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/mul.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/neg.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/operator_/sub.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/python/exception_handler.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/NoneType.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/array.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/assignable.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/attr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/bool.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/cfun.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/clongdouble.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/combined.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/complex.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/complex128.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/complex256.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/complex64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/dict.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/dynamic_tuple.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/empty_iterator.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/exceptions.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/file.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/finfo.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/float.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/float128.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/float32.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/float64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/generator.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/int.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/int16.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/int32.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/int64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/int8.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/intc.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/intp.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/list.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/longdouble.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/ndarray.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/nditerator.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_binary_op.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_broadcast.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_expr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_gexpr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_iexpr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_nary_expr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_op_helper.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_operators.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_texpr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_unary_op.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/numpy_vexpr.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/pointer.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/raw_array.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/set.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/slice.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/static_if.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/str.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/traits.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/tuple.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uint16.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uint32.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uint64.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uint8.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uintc.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/uintp.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/variant_functor.hpp + ${ARCADIA_ROOT}/contrib/python/pythran/pythran/pythonic/types/vectorizable_type.hpp +) + END() diff --git a/contrib/python/ydb/py3/.dist-info/METADATA b/contrib/python/ydb/py3/.dist-info/METADATA index 8a15b64dc4b..aa485a3f4c0 100644 --- a/contrib/python/ydb/py3/.dist-info/METADATA +++ b/contrib/python/ydb/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ydb -Version: 3.21.0 +Version: 3.21.1 Summary: YDB Python SDK Home-page: http://github.com/ydb-platform/ydb-python-sdk Author: Yandex LLC diff --git a/contrib/python/ydb/py3/ya.make b/contrib/python/ydb/py3/ya.make index b938db3247b..5f37e78726e 100644 --- a/contrib/python/ydb/py3/ya.make +++ b/contrib/python/ydb/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(3.21.0) +VERSION(3.21.1) LICENSE(Apache-2.0) diff --git a/contrib/python/ydb/py3/ydb/types.py b/contrib/python/ydb/py3/ydb/types.py index a48548640c0..47c9c48c2e2 100644 --- a/contrib/python/ydb/py3/ydb/types.py +++ b/contrib/python/ydb/py3/ydb/types.py @@ -39,6 +39,19 @@ def _to_date(pb: ydb_value_pb2.Value, value: typing.Union[date, int]) -> None: pb.uint32_value = value +def _from_date32(x: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings) -> typing.Union[date, int]: + if table_client_settings is not None and table_client_settings._native_date_in_result_sets: + return _EPOCH.date() + timedelta(days=x.int32_value) + return x.int32_value + + +def _to_date32(pb: ydb_value_pb2.Value, value: typing.Union[date, int]) -> None: + if isinstance(value, date): + pb.int32_value = (value - _EPOCH.date()).days + else: + pb.int32_value = value + + def _from_datetime_number( x: typing.Union[float, datetime], table_client_settings: table.TableClientSettings ) -> datetime: @@ -62,6 +75,10 @@ def _from_uuid(pb: ydb_value_pb2.Value, value: uuid.UUID): pb.high_128 = struct.unpack("Q", value.bytes_le[8:16])[0] +def _timedelta_to_microseconds(value: timedelta) -> int: + return (value.days * _SECONDS_IN_DAY + value.seconds) * 1000000 + value.microseconds + + def _from_interval( value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings ) -> typing.Union[timedelta, int]: @@ -70,10 +87,6 @@ def _from_interval( return value_pb.int64_value -def _timedelta_to_microseconds(value: timedelta) -> int: - return (value.days * _SECONDS_IN_DAY + value.seconds) * 1000000 + value.microseconds - - def _to_interval(pb: ydb_value_pb2.Value, value: typing.Union[timedelta, int]): if isinstance(value, timedelta): pb.int64_value = _timedelta_to_microseconds(value) @@ -100,6 +113,25 @@ def _to_timestamp(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]): pb.uint64_value = value +def _from_timestamp64( + value_pb: ydb_value_pb2.Value, table_client_settings: table.TableClientSettings +) -> typing.Union[datetime, int]: + if table_client_settings is not None and table_client_settings._native_timestamp_in_result_sets: + return _EPOCH + timedelta(microseconds=value_pb.int64_value) + return value_pb.int64_value + + +def _to_timestamp64(pb: ydb_value_pb2.Value, value: typing.Union[datetime, int]): + if isinstance(value, datetime): + if value.tzinfo: + epoch = _EPOCH_UTC + else: + epoch = _EPOCH + pb.int64_value = _timedelta_to_microseconds(value - epoch) + else: + pb.int64_value = value + + @enum.unique class PrimitiveType(enum.Enum): """ @@ -132,23 +164,46 @@ class PrimitiveType(enum.Enum): _from_date, _to_date, ) + Date32 = ( + _apis.primitive_types.DATE32, + None, + _from_date32, + _to_date32, + ) Datetime = ( _apis.primitive_types.DATETIME, "uint32_value", _from_datetime_number, ) + Datetime64 = ( + _apis.primitive_types.DATETIME64, + "int64_value", + _from_datetime_number, + ) Timestamp = ( _apis.primitive_types.TIMESTAMP, None, _from_timestamp, _to_timestamp, ) + Timestamp64 = ( + _apis.primitive_types.TIMESTAMP64, + None, + _from_timestamp64, + _to_timestamp64, + ) Interval = ( _apis.primitive_types.INTERVAL, None, _from_interval, _to_interval, ) + Interval64 = ( + _apis.primitive_types.INTERVAL64, + None, + _from_interval, + _to_interval, + ) DyNumber = _apis.primitive_types.DYNUMBER, "text_value" @@ -365,6 +420,32 @@ class DictType(AbstractTypeBuilder): return self._repr +class SetType(AbstractTypeBuilder): + __slots__ = ("__repr", "__proto") + + def __init__( + self, + key_type: typing.Union[AbstractTypeBuilder, PrimitiveType], + ): + """ + :param key_type: Key type builder + """ + self._repr = "Set<%s>" % (str(key_type)) + self._proto = _apis.ydb_value.Type( + dict_type=_apis.ydb_value.DictType( + key=key_type.proto, + payload=_apis.ydb_value.Type(void_type=struct_pb2.NULL_VALUE), + ) + ) + + @property + def proto(self): + return self._proto + + def __str__(self): + return self._repr + + class TupleType(AbstractTypeBuilder): __slots__ = ("__elements_repr", "__proto") diff --git a/contrib/python/ydb/py3/ydb/ydb_version.py b/contrib/python/ydb/py3/ydb/ydb_version.py index 6b71007009d..3c62627bc85 100644 --- a/contrib/python/ydb/py3/ydb/ydb_version.py +++ b/contrib/python/ydb/py3/ydb/ydb_version.py @@ -1 +1 @@ -VERSION = "3.21.0" +VERSION = "3.21.1" |