summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/allure-pytest/.dist-info/METADATA4
-rw-r--r--contrib/python/allure-pytest/ya.make2
-rw-r--r--contrib/python/allure-python-commons/.dist-info/METADATA2
-rw-r--r--contrib/python/allure-python-commons/ya.make2
-rw-r--r--contrib/python/google-auth/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/google-auth/py3/google/auth/_helpers.py240
-rw-r--r--contrib/python/google-auth/py3/google/auth/aio/_helpers.py57
-rw-r--r--contrib/python/google-auth/py3/google/auth/aio/transport/aiohttp.py6
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/_aiohttp_requests.py9
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/_http_client.py4
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/requests.py6
-rw-r--r--contrib/python/google-auth/py3/google/auth/transport/urllib3.py4
-rw-r--r--contrib/python/google-auth/py3/google/auth/version.py2
-rw-r--r--contrib/python/google-auth/py3/google/oauth2/webauthn_types.py2
-rw-r--r--contrib/python/google-auth/py3/tests/aio/test__helpers.py110
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test_webauthn_handler.py2
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test_webauthn_types.py2
-rw-r--r--contrib/python/google-auth/py3/tests/test__helpers.py434
-rw-r--r--contrib/python/google-auth/py3/ya.make3
-rw-r--r--contrib/python/numpy/py2/numpy/random/__init__.py2
-rw-r--r--contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx8
-rw-r--r--contrib/python/numpy/py2/patches/fix_cython_import.patch49
-rw-r--r--contrib/python/typing-extensions/py3/.dist-info/METADATA7
-rw-r--r--contrib/python/typing-extensions/py3/typing_extensions.py1111
-rw-r--r--contrib/python/typing-extensions/py3/ya.make2
-rw-r--r--contrib/python/ydb/py3/.dist-info/METADATA6
-rw-r--r--contrib/python/ydb/py3/README.md4
-rw-r--r--contrib/python/ydb/py3/ya.make2
-rw-r--r--contrib/python/ydb/py3/ydb/aio/pool.py31
-rw-r--r--contrib/python/ydb/py3/ydb/driver.py4
-rw-r--r--contrib/python/ydb/py3/ydb/pool.py31
-rw-r--r--contrib/python/ydb/py3/ydb/ydb_version.py2
32 files changed, 2027 insertions, 125 deletions
diff --git a/contrib/python/allure-pytest/.dist-info/METADATA b/contrib/python/allure-pytest/.dist-info/METADATA
index 8da0e7a49e8..834bf01e5c4 100644
--- a/contrib/python/allure-pytest/.dist-info/METADATA
+++ b/contrib/python/allure-pytest/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: allure-pytest
-Version: 2.14.1
+Version: 2.14.2
Summary: Allure pytest integration
Home-page: https://allurereport.org/
Author: Qameta Software Inc., Stanislav Seliverstov
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Description-Content-Type: text/markdown
Requires-Dist: pytest>=4.5.0
-Requires-Dist: allure-python-commons==2.14.1
+Requires-Dist: allure-python-commons==2.14.2
Dynamic: author
Dynamic: author-email
Dynamic: classifier
diff --git a/contrib/python/allure-pytest/ya.make b/contrib/python/allure-pytest/ya.make
index cf244ed539c..4b5ffa06a85 100644
--- a/contrib/python/allure-pytest/ya.make
+++ b/contrib/python/allure-pytest/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.14.1)
+VERSION(2.14.2)
LICENSE(Apache-2.0)
diff --git a/contrib/python/allure-python-commons/.dist-info/METADATA b/contrib/python/allure-python-commons/.dist-info/METADATA
index 5a98d829103..65261a79955 100644
--- a/contrib/python/allure-python-commons/.dist-info/METADATA
+++ b/contrib/python/allure-python-commons/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: allure-python-commons
-Version: 2.14.1
+Version: 2.14.2
Summary: Contains the API for end users as well as helper functions and classes to build Allure adapters for Python test frameworks
Home-page: https://allurereport.org/
Author: Qameta Software Inc., Stanislav Seliverstov
diff --git a/contrib/python/allure-python-commons/ya.make b/contrib/python/allure-python-commons/ya.make
index de11ac76eef..2883e1e72cb 100644
--- a/contrib/python/allure-python-commons/ya.make
+++ b/contrib/python/allure-python-commons/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.14.1)
+VERSION(2.14.2)
LICENSE(Apache-2.0)
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA
index 53da88bdec8..ddba2bddaed 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.39.0
+Version: 2.40.0
Summary: Google Authentication Library
Home-page: https://github.com/googleapis/google-auth-library-python
Author: Google Cloud Platform
diff --git a/contrib/python/google-auth/py3/google/auth/_helpers.py b/contrib/python/google-auth/py3/google/auth/_helpers.py
index a6c07f7d829..78fe22f7264 100644
--- a/contrib/python/google-auth/py3/google/auth/_helpers.py
+++ b/contrib/python/google-auth/py3/google/auth/_helpers.py
@@ -18,15 +18,38 @@ import base64
import calendar
import datetime
from email.message import Message
+import hashlib
+import json
+import logging
import sys
+from typing import Any, Dict, Mapping, Optional, Union
import urllib
from google.auth import exceptions
+
+# _BASE_LOGGER_NAME is the base logger for all google-based loggers.
+_BASE_LOGGER_NAME = "google"
+
+# _LOGGING_INITIALIZED ensures that base logger is only configured once
+# (unless already configured by the end-user).
+_LOGGING_INITIALIZED = False
+
+
# The smallest MDS cache used by this library stores tokens until 4 minutes from
# expiry.
REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45)
+# TODO(https://github.com/googleapis/google-auth-library-python/issues/1684): Audit and update the list below.
+_SENSITIVE_FIELDS = {
+ "accessToken",
+ "access_token",
+ "id_token",
+ "client_id",
+ "refresh_token",
+ "client_secret",
+}
+
def copy_docstring(source_class):
"""Decorator that copies a method's docstring from another class.
@@ -271,3 +294,220 @@ def is_python_3():
bool: True if the Python interpreter is Python 3 and False otherwise.
"""
return sys.version_info > (3, 0)
+
+
+def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]:
+ """
+ Hashes sensitive information within a dictionary.
+
+ Args:
+ data: The dictionary containing data to be processed.
+
+ Returns:
+ A new dictionary with sensitive values replaced by their SHA512 hashes.
+ If the input is a list, returns a list with each element recursively processed.
+ If the input is neither a dict nor a list, returns the type of the input as a string.
+
+ """
+ if isinstance(data, dict):
+ hashed_data: Dict[Any, Union[Optional[str], dict, list]] = {}
+ for key, value in data.items():
+ if key in _SENSITIVE_FIELDS and not isinstance(value, (dict, list)):
+ hashed_data[key] = _hash_value(value, key)
+ elif isinstance(value, (dict, list)):
+ hashed_data[key] = _hash_sensitive_info(value)
+ else:
+ hashed_data[key] = value
+ return hashed_data
+ elif isinstance(data, list):
+ hashed_list = []
+ for val in data:
+ hashed_list.append(_hash_sensitive_info(val))
+ return hashed_list
+ else:
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1701):
+ # Investigate and hash sensitive info before logging when the data type is
+ # not a dict or a list.
+ return str(type(data))
+
+
+def _hash_value(value, field_name: str) -> Optional[str]:
+ """Hashes a value and returns a formatted hash string."""
+ if value is None:
+ return None
+ encoded_value = str(value).encode("utf-8")
+ hash_object = hashlib.sha512()
+ hash_object.update(encoded_value)
+ hex_digest = hash_object.hexdigest()
+ return f"hashed_{field_name}-{hex_digest}"
+
+
+def _logger_configured(logger: logging.Logger) -> bool:
+ """Determines whether `logger` has non-default configuration
+
+ Args:
+ logger: The logger to check.
+
+ Returns:
+ bool: Whether the logger has any non-default configuration.
+ """
+ return (
+ logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
+ )
+
+
+def is_logging_enabled(logger: logging.Logger) -> bool:
+ """
+ Checks if debug logging is enabled for the given logger.
+
+ Args:
+ logger: The logging.Logger instance to check.
+
+ Returns:
+ True if debug logging is enabled, False otherwise.
+ """
+ # NOTE: Log propagation to the root logger is disabled unless
+ # the base logger i.e. logging.getLogger("google") is
+ # explicitly configured by the end user. Ideally this
+ # needs to happen in the client layer (already does for GAPICs).
+ # However, this is implemented here to avoid logging
+ # (if a root logger is configured) when a version of google-auth
+ # which supports logging is used with:
+ # - an older version of a GAPIC which does not support logging.
+ # - Apiary client which does not support logging.
+ global _LOGGING_INITIALIZED
+ if not _LOGGING_INITIALIZED:
+ base_logger = logging.getLogger(_BASE_LOGGER_NAME)
+ if not _logger_configured(base_logger):
+ base_logger.propagate = False
+ _LOGGING_INITIALIZED = True
+
+ return logger.isEnabledFor(logging.DEBUG)
+
+
+def request_log(
+ logger: logging.Logger,
+ method: str,
+ url: str,
+ body: Optional[bytes],
+ headers: Optional[Mapping[str, str]],
+) -> None:
+ """
+ Logs an HTTP request at the DEBUG level if logging is enabled.
+
+ Args:
+ logger: The logging.Logger instance to use.
+ method: The HTTP method (e.g., "GET", "POST").
+ url: The URL of the request.
+ body: The request body (can be None).
+ headers: The request headers (can be None).
+ """
+ if is_logging_enabled(logger):
+ content_type = (
+ headers["Content-Type"] if headers and "Content-Type" in headers else ""
+ )
+ json_body = _parse_request_body(body, content_type=content_type)
+ logged_body = _hash_sensitive_info(json_body)
+ logger.debug(
+ "Making request...",
+ extra={
+ "httpRequest": {
+ "method": method,
+ "url": url,
+ "body": logged_body,
+ "headers": headers,
+ }
+ },
+ )
+
+
+def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any:
+ """
+ Parses a request body, handling bytes and string types, and different content types.
+
+ Args:
+ body (Optional[bytes]): The request body.
+ content_type (str): The content type of the request body, e.g., "application/json",
+ "application/x-www-form-urlencoded", or "text/plain". If empty, attempts
+ to parse as JSON.
+
+ Returns:
+ Parsed body (dict, str, or None).
+ - JSON: Decodes if content_type is "application/json" or None (fallback).
+ - URL-encoded: Parses if content_type is "application/x-www-form-urlencoded".
+ - Plain text: Returns string if content_type is "text/plain".
+ - None: Returns if body is None, UTF-8 decode fails, or content_type is unknown.
+ """
+ if body is None:
+ return None
+ try:
+ body_str = body.decode("utf-8")
+ except (UnicodeDecodeError, AttributeError):
+ return None
+ content_type = content_type.lower()
+ if not content_type or "application/json" in content_type:
+ try:
+ return json.loads(body_str)
+ except (json.JSONDecodeError, TypeError):
+ return body_str
+ if "application/x-www-form-urlencoded" in content_type:
+ parsed_query = urllib.parse.parse_qs(body_str)
+ result = {k: v[0] for k, v in parsed_query.items()}
+ return result
+ if "text/plain" in content_type:
+ return body_str
+ return None
+
+
+def _parse_response(response: Any) -> Any:
+ """
+ Parses a response, attempting to decode JSON.
+
+ Args:
+ response: The response object to parse. This can be any type, but
+ it is expected to have a `json()` method if it contains JSON.
+
+ Returns:
+ The parsed response. If the response contains valid JSON, the
+ decoded JSON object (e.g., a dictionary or list) is returned.
+ If the response does not have a `json()` method or if the JSON
+ decoding fails, None is returned.
+ """
+ try:
+ json_response = response.json()
+ return json_response
+ except Exception:
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1744):
+ # Parse and return response payload as json based on different content types.
+ return None
+
+
+def _response_log_base(logger: logging.Logger, parsed_response: Any) -> None:
+ """
+ Logs a parsed HTTP response at the DEBUG level.
+
+ This internal helper function takes a parsed response and logs it
+ using the provided logger. It also applies a hashing function to
+ potentially sensitive information before logging.
+
+ Args:
+ logger: The logging.Logger instance to use for logging.
+ parsed_response: The parsed HTTP response object (e.g., a dictionary,
+ list, or the original response if parsing failed).
+ """
+
+ logged_response = _hash_sensitive_info(parsed_response)
+ logger.debug("Response received...", extra={"httpResponse": logged_response})
+
+
+def response_log(logger: logging.Logger, response: Any) -> None:
+ """
+ Logs an HTTP response at the DEBUG level if logging is enabled.
+
+ Args:
+ logger: The logging.Logger instance to use.
+ response: The HTTP response object to log.
+ """
+ if is_logging_enabled(logger):
+ json_response = _parse_response(response)
+ _response_log_base(logger, json_response)
diff --git a/contrib/python/google-auth/py3/google/auth/aio/_helpers.py b/contrib/python/google-auth/py3/google/auth/aio/_helpers.py
new file mode 100644
index 00000000000..fd7d37a2f7b
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/aio/_helpers.py
@@ -0,0 +1,57 @@
+# Copyright 2025 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Helper functions for commonly used utilities."""
+
+
+import logging
+from typing import Any
+
+from google.auth import _helpers
+
+
+async def _parse_response_async(response: Any) -> Any:
+ """
+ Parses an async response, attempting to decode JSON.
+
+ Args:
+ response: The response object to parse. This can be any type, but
+ it is expected to have a `json()` method if it contains JSON.
+
+ Returns:
+ The parsed response. If the response contains valid JSON, the
+ decoded JSON object (e.g., a dictionary) is returned.
+ If the response does not have a `json()` method or if the JSON
+ decoding fails, None is returned.
+ """
+ try:
+ json_response = await response.json()
+ return json_response
+ except Exception:
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1745):
+ # Parse and return response payload as json based on different content types.
+ return None
+
+
+async def response_log_async(logger: logging.Logger, response: Any) -> None:
+ """
+ Logs an Async HTTP response at the DEBUG level if logging is enabled.
+
+ Args:
+ logger: The logging.Logger instance to use.
+ response: The HTTP response object to log.
+ """
+ if _helpers.is_logging_enabled(logger):
+ json_response = await _parse_response_async(response)
+ _helpers._response_log_base(logger, json_response)
diff --git a/contrib/python/google-auth/py3/google/auth/aio/transport/aiohttp.py b/contrib/python/google-auth/py3/google/auth/aio/transport/aiohttp.py
index 074d1491c70..67a19f952d2 100644
--- a/contrib/python/google-auth/py3/google/auth/aio/transport/aiohttp.py
+++ b/contrib/python/google-auth/py3/google/auth/aio/transport/aiohttp.py
@@ -16,6 +16,7 @@
"""
import asyncio
+import logging
from typing import AsyncGenerator, Mapping, Optional
try:
@@ -27,8 +28,11 @@ except ImportError as caught_exc: # pragma: NO COVER
from google.auth import _helpers
from google.auth import exceptions
+from google.auth.aio import _helpers as _helpers_async
from google.auth.aio import transport
+_LOGGER = logging.getLogger(__name__)
+
class Response(transport.Response):
"""
@@ -155,6 +159,7 @@ class Request(transport.Request):
self._session = aiohttp.ClientSession()
client_timeout = aiohttp.ClientTimeout(total=timeout)
+ _helpers.request_log(_LOGGER, method, url, body, headers)
response = await self._session.request(
method,
url,
@@ -163,6 +168,7 @@ class Request(transport.Request):
timeout=client_timeout,
**kwargs,
)
+ await _helpers_async.response_log_async(_LOGGER, response)
return Response(response)
except aiohttp.ClientError as caught_exc:
diff --git a/contrib/python/google-auth/py3/google/auth/transport/_aiohttp_requests.py b/contrib/python/google-auth/py3/google/auth/transport/_aiohttp_requests.py
index bc4d9dc69af..36366be5108 100644
--- a/contrib/python/google-auth/py3/google/auth/transport/_aiohttp_requests.py
+++ b/contrib/python/google-auth/py3/google/auth/transport/_aiohttp_requests.py
@@ -22,14 +22,20 @@ from __future__ import absolute_import
import asyncio
import functools
+import logging
import aiohttp # type: ignore
import urllib3 # type: ignore
+from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
+from google.auth.aio import _helpers as _helpers_async
from google.auth.transport import requests
+
+_LOGGER = logging.getLogger(__name__)
+
# Timeout can be re-defined depending on async requirement. Currently made 60s more than
# sync timeout.
_DEFAULT_TIMEOUT = 180 # in seconds
@@ -182,10 +188,11 @@ class Request(transport.Request):
self.session = aiohttp.ClientSession(
auto_decompress=False
) # pragma: NO COVER
- requests._LOGGER.debug("Making request: %s %s", method, url)
+ _helpers.request_log(_LOGGER, method, url, body, headers)
response = await self.session.request(
method, url, data=body, headers=headers, timeout=timeout, **kwargs
)
+ await _helpers_async.response_log_async(_LOGGER, response)
return _CombinedResponse(response)
except aiohttp.ClientError as caught_exc:
diff --git a/contrib/python/google-auth/py3/google/auth/transport/_http_client.py b/contrib/python/google-auth/py3/google/auth/transport/_http_client.py
index cec0ab73fb3..898a86519b7 100644
--- a/contrib/python/google-auth/py3/google/auth/transport/_http_client.py
+++ b/contrib/python/google-auth/py3/google/auth/transport/_http_client.py
@@ -19,6 +19,7 @@ import logging
import socket
import urllib
+from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
@@ -99,10 +100,11 @@ class Request(transport.Request):
connection = http_client.HTTPConnection(parts.netloc, timeout=timeout)
try:
- _LOGGER.debug("Making request: %s %s", method, url)
+ _helpers.request_log(_LOGGER, method, url, body, headers)
connection.request(method, path, body=body, headers=headers, **kwargs)
response = connection.getresponse()
+ _helpers.response_log(_LOGGER, response)
return Response(response)
except (http_client.HTTPException, socket.error) as caught_exc:
diff --git a/contrib/python/google-auth/py3/google/auth/transport/requests.py b/contrib/python/google-auth/py3/google/auth/transport/requests.py
index 23a69783dc3..0540746f894 100644
--- a/contrib/python/google-auth/py3/google/auth/transport/requests.py
+++ b/contrib/python/google-auth/py3/google/auth/transport/requests.py
@@ -34,6 +34,7 @@ from requests.packages.urllib3.util.ssl_ import ( # type: ignore
create_urllib3_context,
) # pylint: disable=ungrouped-imports
+from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import transport
@@ -182,10 +183,11 @@ class Request(transport.Request):
google.auth.exceptions.TransportError: If any exception occurred.
"""
try:
- _LOGGER.debug("Making request: %s %s", method, url)
+ _helpers.request_log(_LOGGER, method, url, body, headers)
response = self.session.request(
method, url, data=body, headers=headers, timeout=timeout, **kwargs
)
+ _helpers.response_log(_LOGGER, response)
return _Response(response)
except requests.exceptions.RequestException as caught_exc:
new_exc = exceptions.TransportError(caught_exc)
@@ -534,6 +536,7 @@ class AuthorizedSession(requests.Session):
remaining_time = guard.remaining_timeout
with TimeoutGuard(remaining_time) as guard:
+ _helpers.request_log(_LOGGER, method, url, data, headers)
response = super(AuthorizedSession, self).request(
method,
url,
@@ -542,6 +545,7 @@ class AuthorizedSession(requests.Session):
timeout=timeout,
**kwargs
)
+ _helpers.response_log(_LOGGER, response)
remaining_time = guard.remaining_timeout
# If the response indicated that the credentials needed to be
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 db4fa93ff11..03ed75aa2f5 100644
--- a/contrib/python/google-auth/py3/google/auth/transport/urllib3.py
+++ b/contrib/python/google-auth/py3/google/auth/transport/urllib3.py
@@ -50,6 +50,7 @@ except ImportError as caught_exc: # pragma: NO COVER
) from caught_exc
+from google.auth import _helpers
from google.auth import environment_vars
from google.auth import exceptions
from google.auth import transport
@@ -144,10 +145,11 @@ class Request(transport.Request):
kwargs["timeout"] = timeout
try:
- _LOGGER.debug("Making request: %s %s", method, url)
+ _helpers.request_log(_LOGGER, method, url, body, headers)
response = self.http.request(
method, url, body=body, headers=headers, **kwargs
)
+ _helpers.response_log(_LOGGER, response)
return _Response(response)
except urllib3.exceptions.HTTPError as caught_exc:
new_exc = exceptions.TransportError(caught_exc)
diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py
index 393caa8ad44..d1363c1ef0e 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.39.0"
+__version__ = "2.40.0"
diff --git a/contrib/python/google-auth/py3/google/oauth2/webauthn_types.py b/contrib/python/google-auth/py3/google/oauth2/webauthn_types.py
index 7784e83d0b9..24e984f3d33 100644
--- a/contrib/python/google-auth/py3/google/oauth2/webauthn_types.py
+++ b/contrib/python/google-auth/py3/google/oauth2/webauthn_types.py
@@ -67,7 +67,7 @@ class GetRequest:
extensions: Optional[AuthenticationExtensionsClientInputs] = None
def to_json(self) -> str:
- req_options: Dict[str, Any] = {"rpid": self.rpid, "challenge": self.challenge}
+ req_options: Dict[str, Any] = {"rpId": self.rpid, "challenge": self.challenge}
if self.timeout_ms:
req_options["timeout"] = self.timeout_ms
if self.allow_credentials:
diff --git a/contrib/python/google-auth/py3/tests/aio/test__helpers.py b/contrib/python/google-auth/py3/tests/aio/test__helpers.py
new file mode 100644
index 00000000000..7642431caec
--- /dev/null
+++ b/contrib/python/google-auth/py3/tests/aio/test__helpers.py
@@ -0,0 +1,110 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import logging
+
+import pytest # type: ignore
+
+from google.auth.aio import _helpers
+
+# _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing.
+_MOCK_BASE_LOGGER_NAME = "foogle"
+
+# _MOCK_CHILD_LOGGER_NAME is the child logger namespace used for testing.
+_MOCK_CHILD_LOGGER_NAME = "foogle.bar"
+
+
+def logger():
+ """Returns a child logger for testing."""
+ logger = logging.getLogger(_MOCK_CHILD_LOGGER_NAME)
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ return logger
+
+
+def base_logger():
+ """Returns a child logger for testing."""
+ logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME)
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ return logger
+
+
+async def test_response_log_debug_enabled(logger, caplog, base_logger):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ await _helpers.response_log_async(logger, {"payload": None})
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Response received..."
+ assert record.httpResponse == "<class 'NoneType'>"
+
+
+async def test_response_log_debug_disabled(logger, caplog, base_logger):
+ caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
+ await _helpers.response_log_async(logger, "another_response")
+ assert "Response received..." not in caplog.text
+
+
+async def test_response_log_debug_enabled_response_json(logger, caplog, base_logger):
+ class MockResponse:
+ async def json(self):
+ return {"key1": "value1", "key2": "value2", "key3": "value3"}
+
+ response = MockResponse()
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ await _helpers.response_log_async(logger, response)
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Response received..."
+ assert record.httpResponse == {"key1": "value1", "key2": "value2", "key3": "value3"}
+
+
+async def test_parse_response_async_json_valid():
+ class MockResponse:
+ async def json(self):
+ return {"data": "test"}
+
+ response = MockResponse()
+ expected = {"data": "test"}
+ assert await _helpers._parse_response_async(response) == expected
+
+
+async def test_parse_response_async_json_invalid():
+ class MockResponse:
+ def json(self):
+ raise json.JSONDecodeError("msg", "doc", 0)
+
+ response = MockResponse()
+ assert await _helpers._parse_response_async(response) is None
+
+
+async def test_parse_response_async_no_json_method():
+ response = "plain text"
+ assert await _helpers._parse_response_async(response) is None
+
+
+async def test_parse_response_async_none():
+ assert await _helpers._parse_response_async(None) is None
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_handler.py b/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_handler.py
index 454e97cb61d..9fba266da9f 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_handler.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_handler.py
@@ -118,7 +118,7 @@ def test_success_get_assertion(os_get_stub, subprocess_run_stub):
"type": "get",
"origin": "fake_origin",
"requestData": {
- "rpid": "fake_rpid",
+ "rpId": "fake_rpid",
"challenge": "fake_challenge",
"allowCredentials": [{"type": "public-key", "id": "fake_id_1"}],
},
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_types.py b/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_types.py
index 5231d21896a..bafe5b05060 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_types.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test_webauthn_types.py
@@ -82,7 +82,7 @@ def test_GetRequest(has_allow_credentials):
"type": "get",
"origin": "fake_origin",
"requestData": {
- "rpid": "fake_rpid",
+ "rpId": "fake_rpid",
"timeout": 123,
"challenge": "fake_challenge",
"userVerification": "preferred",
diff --git a/contrib/python/google-auth/py3/tests/test__helpers.py b/contrib/python/google-auth/py3/tests/test__helpers.py
index c9a3847ac48..a4337c01608 100644
--- a/contrib/python/google-auth/py3/tests/test__helpers.py
+++ b/contrib/python/google-auth/py3/tests/test__helpers.py
@@ -13,12 +13,50 @@
# limitations under the License.
import datetime
+import json
+import logging
+from unittest import mock
import urllib
import pytest # type: ignore
from google.auth import _helpers
+# _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing.
+_MOCK_BASE_LOGGER_NAME = "foogle"
+
+# _MOCK_CHILD_LOGGER_NAME is the child logger namespace used for testing.
+_MOCK_CHILD_LOGGER_NAME = "foogle.bar"
+
+
+def logger():
+ """Returns a child logger for testing."""
+ logger = logging.getLogger(_MOCK_CHILD_LOGGER_NAME)
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ return logger
+
+
+def base_logger():
+ """Returns a child logger for testing."""
+ logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME)
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ return logger
+
+
[email protected](autouse=True)
+def reset_logging_initialized():
+ """Resets the global _LOGGING_INITIALIZED variable before each test."""
+ original_state = _helpers._LOGGING_INITIALIZED
+ _helpers._LOGGING_INITIALIZED = False
+ yield
+ _helpers._LOGGING_INITIALIZED = original_state
+
class SourceClass(object):
def func(self): # pragma: NO COVER
@@ -92,7 +130,7 @@ def test_to_bytes_with_bytes():
def test_to_bytes_with_unicode():
- value = u"string-val"
+ value = "string-val"
encoded_value = b"string-val"
assert _helpers.to_bytes(value) == encoded_value
@@ -103,13 +141,13 @@ def test_to_bytes_with_nonstring_type():
def test_from_bytes_with_unicode():
- value = u"bytes-val"
+ value = "bytes-val"
assert _helpers.from_bytes(value) == value
def test_from_bytes_with_bytes():
value = b"string-val"
- decoded_value = u"string-val"
+ decoded_value = "string-val"
assert _helpers.from_bytes(value) == decoded_value
@@ -194,3 +232,393 @@ def test_unpadded_urlsafe_b64encode():
for case, expected in cases:
assert _helpers.unpadded_urlsafe_b64encode(case) == expected
+
+
+def test_hash_sensitive_info_basic():
+ test_data = {
+ "expires_in": 3599,
+ "access_token": "access-123",
+ "scope": "https://www.googleapis.com/auth/test-api",
+ "token_type": "Bearer",
+ }
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data["expires_in"] == 3599
+ assert hashed_data["scope"] == "https://www.googleapis.com/auth/test-api"
+ assert hashed_data["access_token"].startswith("hashed_access_token-")
+ assert hashed_data["token_type"] == "Bearer"
+
+
+def test_hash_sensitive_info_multiple_sensitive():
+ test_data = {
+ "access_token": "some_long_token",
+ "id_token": "1234-5678-9012-3456",
+ "expires_in": 3599,
+ "token_type": "Bearer",
+ }
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data["expires_in"] == 3599
+ assert hashed_data["token_type"] == "Bearer"
+ assert hashed_data["access_token"].startswith("hashed_access_token-")
+ assert hashed_data["id_token"].startswith("hashed_id_token-")
+
+
+def test_hash_sensitive_info_none_value():
+ test_data = {"username": "user3", "secret": None, "normal_data": "abc"}
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data["secret"] is None
+ assert hashed_data["normal_data"] == "abc"
+
+
+def test_hash_sensitive_info_non_string_value():
+ test_data = {"username": "user4", "access_token": 12345, "normal_data": "def"}
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data["access_token"].startswith("hashed_access_token-")
+ assert hashed_data["normal_data"] == "def"
+
+
+def test_hash_sensitive_info_list_value():
+ test_data = [
+ {"name": "Alice", "access_token": "12345"},
+ {"name": "Bob", "client_id": "1141"},
+ ]
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data[0]["access_token"].startswith("hashed_access_token-")
+ assert hashed_data[1]["client_id"].startswith("hashed_client_id-")
+
+
+def test_hash_sensitive_info_nested_list_value():
+ test_data = [{"names": ["Alice", "Bob"], "tokens": [{"access_token": "1234"}]}]
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data[0]["tokens"][0]["access_token"].startswith(
+ "hashed_access_token-"
+ )
+
+
+def test_hash_sensitive_info_int_value():
+ test_data = 123
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data == "<class 'int'>"
+
+
+def test_hash_sensitive_info_bool_value():
+ test_data = True
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data == "<class 'bool'>"
+
+
+def test_hash_sensitive_info_byte_value():
+ test_data = b"1243"
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data == "<class 'bytes'>"
+
+
+def test_hash_sensitive_info_empty_dict():
+ test_data = {}
+ hashed_data = _helpers._hash_sensitive_info(test_data)
+ assert hashed_data == {}
+
+
+def test_hash_value_consistent_hashing():
+ value = "test_value"
+ field_name = "test_field"
+ hash1 = _helpers._hash_value(value, field_name)
+ hash2 = _helpers._hash_value(value, field_name)
+ assert hash1 == hash2
+
+
+def test_hash_value_different_hashing():
+ value1 = "test_value1"
+ value2 = "test_value2"
+ field_name = "test_field"
+ hash1 = _helpers._hash_value(value1, field_name)
+ hash2 = _helpers._hash_value(value2, field_name)
+ assert hash1 != hash2
+
+
+def test_hash_value_none():
+ assert _helpers._hash_value(None, "test") is None
+
+
+def test_logger_configured_default(logger):
+ assert not _helpers._logger_configured(logger)
+
+
+def test_logger_configured_with_handler(logger):
+ mock_handler = logging.NullHandler()
+ logger.addHandler(mock_handler)
+ assert _helpers._logger_configured(logger)
+
+ # Cleanup
+ logger.removeHandler(mock_handler)
+
+
+def test_logger_configured_with_custom_level(logger):
+ original_level = logger.level
+ logger.level = logging.INFO
+ assert _helpers._logger_configured(logger)
+
+ # Cleanup
+ logging.level = original_level
+
+
+def test_logger_configured_with_propagate(logger):
+ original_propagate = logger.propagate
+ logger.propagate = False
+ assert _helpers._logger_configured(logger)
+
+ # Cleanup
+ logger.propagate = original_propagate
+
+
+def test_is_logging_enabled_with_no_level_set(logger, base_logger):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", "foogle"):
+ assert _helpers.is_logging_enabled(logger) is False
+
+
+def test_is_logging_enabled_with_debug_disabled(caplog, logger, base_logger):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
+ assert _helpers.is_logging_enabled(logger) is False
+
+
+def test_is_logging_enabled_with_debug_enabled(caplog, logger, base_logger):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ assert _helpers.is_logging_enabled(logger)
+
+
+def test_is_logging_enabled_with_base_logger_configured_with_info(
+ caplog, logger, base_logger
+):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.INFO, logger=_MOCK_BASE_LOGGER_NAME)
+
+ base_logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME)
+ assert not _helpers.is_logging_enabled(base_logger)
+ assert not _helpers.is_logging_enabled(logger)
+
+
+def test_is_logging_enabled_with_base_logger_configured_with_debug(
+ caplog, logger, base_logger
+):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME)
+
+ assert _helpers.is_logging_enabled(base_logger)
+ assert _helpers.is_logging_enabled(logger)
+
+
+def test_is_logging_enabled_with_base_logger_info_child_logger_debug(
+ caplog, logger, base_logger
+):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.INFO, logger=_MOCK_BASE_LOGGER_NAME)
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+
+ assert not _helpers.is_logging_enabled(base_logger)
+ assert _helpers.is_logging_enabled(logger)
+
+
+def test_is_logging_enabled_with_base_logger_debug_child_logger_info(
+ caplog, logger, base_logger
+):
+ with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME)
+ caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
+
+ assert _helpers.is_logging_enabled(base_logger)
+ assert not _helpers.is_logging_enabled(logger)
+
+
+def test_request_log_debug_enabled(logger, caplog, base_logger):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.request_log(
+ logger,
+ "GET",
+ "http://example.com",
+ b'{"key": "value"}',
+ {"Authorization": "Bearer token"},
+ )
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Making request..."
+ assert record.httpRequest == {
+ "method": "GET",
+ "url": "http://example.com",
+ "body": {"key": "value"},
+ "headers": {"Authorization": "Bearer token"},
+ }
+
+
+def test_request_log_plain_text_debug_enabled(logger, caplog, base_logger):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.request_log(
+ logger,
+ "GET",
+ "http://example.com",
+ b"This is plain text.",
+ {"Authorization": "Bearer token", "Content-Type": "text/plain"},
+ )
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Making request..."
+ assert record.httpRequest == {
+ "method": "GET",
+ "url": "http://example.com",
+ "body": "<class 'str'>",
+ "headers": {"Authorization": "Bearer token", "Content-Type": "text/plain"},
+ }
+
+
+def test_request_log_debug_disabled(logger, caplog, base_logger):
+ caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.request_log(
+ logger,
+ "POST",
+ "https://api.example.com",
+ "data",
+ {"Content-Type": "application/json"},
+ )
+ assert "Making request: POST https://api.example.com" not in caplog.text
+
+
+def test_response_log_debug_enabled(logger, caplog, base_logger):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.response_log(logger, {"payload": None})
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Response received..."
+ assert record.httpResponse == "<class 'NoneType'>"
+
+
+def test_response_log_debug_disabled(logger, caplog):
+ caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.response_log(logger, "another_response")
+ assert "Response received..." not in caplog.text
+
+
+def test_response_log_base_logger_configured(logger, caplog, base_logger):
+ caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME)
+ _helpers.response_log(logger, "another_response")
+ assert "Response received..." in caplog.text
+
+
+def test_response_log_debug_enabled_response_list(logger, caplog, base_logger):
+ # NOTE: test the response log when response.json() returns a list as per
+ # https://requests.readthedocs.io/en/latest/api/#requests.Response.json.
+ class MockResponse:
+ def json(self):
+ return ["item1", "item2", "item3"]
+
+ response = MockResponse()
+ caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
+ _helpers.response_log(logger, response)
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.message == "Response received..."
+ assert record.httpResponse == ["<class 'str'>", "<class 'str'>", "<class 'str'>"]
+
+
+def test_parse_request_body_bytes_valid():
+ body = b"key1=value1&key2=value2"
+ expected = {"key1": "value1", "key2": "value2"}
+ assert (
+ _helpers._parse_request_body(
+ body, content_type="application/x-www-form-urlencoded"
+ )
+ == expected
+ )
+
+
+def test_parse_request_body_bytes_empty():
+ body = b""
+ assert _helpers._parse_request_body(body) == ""
+
+
+def test_parse_request_body_bytes_invalid_encoding():
+ body = b"\xff\xfe\xfd" # Invalid UTF-8 sequence
+ assert _helpers._parse_request_body(body) is None
+
+
+def test_parse_request_body_bytes_malformed_query():
+ body = b"key1=value1&key2=value2" # missing equals
+ expected = {"key1": "value1", "key2": "value2"}
+ assert (
+ _helpers._parse_request_body(
+ body, content_type="application/x-www-form-urlencoded"
+ )
+ == expected
+ )
+
+
+def test_parse_request_body_none():
+ assert _helpers._parse_request_body(None) is None
+
+
+def test_parse_request_body_bytes_no_content_type():
+ body = b'{"key": "value"}'
+ expected = {"key": "value"}
+ assert _helpers._parse_request_body(body) == expected
+
+
+def test_parse_request_body_bytes_content_type_json():
+ body = b'{"key": "value"}'
+ expected = {"key": "value"}
+ assert (
+ _helpers._parse_request_body(body, content_type="application/json") == expected
+ )
+
+
+def test_parse_request_body_content_type_urlencoded():
+ body = b"key=value"
+ expected = {"key": "value"}
+ assert (
+ _helpers._parse_request_body(
+ body, content_type="application/x-www-form-urlencoded"
+ )
+ == expected
+ )
+
+
+def test_parse_request_body_bytes_content_type_text():
+ body = b"This is plain text."
+ expected = "This is plain text."
+ assert _helpers._parse_request_body(body, content_type="text/plain") == expected
+
+
+def test_parse_request_body_content_type_invalid():
+ body = b'{"key": "value"}'
+ assert _helpers._parse_request_body(body, content_type="invalid") is None
+
+
+def test_parse_request_body_other_type():
+ assert _helpers._parse_request_body(123) is None
+ assert _helpers._parse_request_body("string") is None
+
+
+def test_parse_response_json_valid():
+ class MockResponse:
+ def json(self):
+ return {"data": "test"}
+
+ response = MockResponse()
+ expected = {"data": "test"}
+ assert _helpers._parse_response(response) == expected
+
+
+def test_parse_response_json_invalid():
+ class MockResponse:
+ def json(self):
+ raise json.JSONDecodeError("msg", "doc", 0)
+
+ response = MockResponse()
+ assert _helpers._parse_response(response) is None
+
+
+def test_parse_response_no_json_method():
+ response = "plain text"
+ assert _helpers._parse_response(response) is None
+
+
+def test_parse_response_none():
+ assert _helpers._parse_response(None) is None
diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make
index eddecb079e5..7ceec35306e 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.39.0)
+VERSION(2.40.0)
LICENSE(Apache-2.0)
@@ -39,6 +39,7 @@ PY_SRCS(
google/auth/_refresh_worker.py
google/auth/_service_account_info.py
google/auth/aio/__init__.py
+ google/auth/aio/_helpers.py
google/auth/aio/credentials.py
google/auth/aio/transport/__init__.py
google/auth/aio/transport/aiohttp.py
diff --git a/contrib/python/numpy/py2/numpy/random/__init__.py b/contrib/python/numpy/py2/numpy/random/__init__.py
index 965ab5ea9b7..1458c8d8f9f 100644
--- a/contrib/python/numpy/py2/numpy/random/__init__.py
+++ b/contrib/python/numpy/py2/numpy/random/__init__.py
@@ -146,7 +146,7 @@ with warnings.catch_warnings():
ranf = random = sample = random_sample
__all__.extend(['ranf', 'random', 'sample'])
-def __RandomState_ctor():
+def _RandomState_ctor():
"""Return a RandomState instance.
This function exists solely to assist (un)pickling.
diff --git a/contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx b/contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx
index 08312da552f..7d82b9f8cfa 100644
--- a/contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx
+++ b/contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx
@@ -813,7 +813,7 @@ cdef class RandomState:
self.set_state(state)
def __reduce__(self):
- return (np.random.__RandomState_ctor, (), self.get_state())
+ return (np.random._RandomState_ctor, (), self.get_state())
# Basic distributions:
def random_sample(self, size=None):
@@ -990,7 +990,7 @@ cdef class RandomState:
raise ValueError("high is out of bounds for %s" % dtype)
if ilow >= ihigh and np.prod(size) != 0:
raise ValueError("Range cannot be empty (low >= high) unless no samples are taken")
-
+
with self.lock:
ret = randfunc(ilow, ihigh - 1, size, self.state_address)
@@ -4919,7 +4919,7 @@ cdef class RandomState:
return arr
arr = np.asarray(x)
-
+
# shuffle has fast-path for 1-d
if arr.ndim == 1:
# Return a copy if same memory
@@ -4932,7 +4932,7 @@ cdef class RandomState:
idx = np.arange(arr.shape[0], dtype=np.intp)
self.shuffle(idx)
return arr[idx]
-
+
_rand = RandomState()
seed = _rand.seed
diff --git a/contrib/python/numpy/py2/patches/fix_cython_import.patch b/contrib/python/numpy/py2/patches/fix_cython_import.patch
new file mode 100644
index 00000000000..ac10dc0eabc
--- /dev/null
+++ b/contrib/python/numpy/py2/patches/fix_cython_import.patch
@@ -0,0 +1,49 @@
+--- contrib/python/numpy/py2/numpy/random/__init__.py (index)
++++ contrib/python/numpy/py2/numpy/random/__init__.py (working tree)
+@@ -146,7 +146,7 @@ with warnings.catch_warnings():
+ ranf = random = sample = random_sample
+ __all__.extend(['ranf', 'random', 'sample'])
+
+-def __RandomState_ctor():
++def _RandomState_ctor():
+ """Return a RandomState instance.
+
+ This function exists solely to assist (un)pickling.
+--- contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx (index)
++++ contrib/python/numpy/py2/numpy/random/mtrand/mtrand.pyx (working tree)
+@@ -813,7 +813,7 @@ cdef class RandomState:
+ self.set_state(state)
+
+ def __reduce__(self):
+- return (np.random.__RandomState_ctor, (), self.get_state())
++ return (np.random._RandomState_ctor, (), self.get_state())
+
+ # Basic distributions:
+ def random_sample(self, size=None):
+@@ -990,7 +990,7 @@ cdef class RandomState:
+ raise ValueError("high is out of bounds for %s" % dtype)
+ if ilow >= ihigh and np.prod(size) != 0:
+ raise ValueError("Range cannot be empty (low >= high) unless no samples are taken")
+-
++
+ with self.lock:
+ ret = randfunc(ilow, ihigh - 1, size, self.state_address)
+
+@@ -4919,7 +4919,7 @@ cdef class RandomState:
+ return arr
+
+ arr = np.asarray(x)
+-
++
+ # shuffle has fast-path for 1-d
+ if arr.ndim == 1:
+ # Return a copy if same memory
+@@ -4932,7 +4932,7 @@ cdef class RandomState:
+ idx = np.arange(arr.shape[0], dtype=np.intp)
+ self.shuffle(idx)
+ return arr[idx]
+-
++
+
+ _rand = RandomState()
+ seed = _rand.seed
diff --git a/contrib/python/typing-extensions/py3/.dist-info/METADATA b/contrib/python/typing-extensions/py3/.dist-info/METADATA
index f15e2b38773..4b0732f4c7f 100644
--- a/contrib/python/typing-extensions/py3/.dist-info/METADATA
+++ b/contrib/python/typing-extensions/py3/.dist-info/METADATA
@@ -1,15 +1,15 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: typing_extensions
-Version: 4.12.2
+Version: 4.13.2
Summary: Backported and Experimental Type Hints for Python 3.8+
Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing
Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" <[email protected]>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
+License-Expression: PSF-2.0
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: Python Software Foundation License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
+License-File: LICENSE
Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues
Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md
Project-URL: Documentation, https://typing-extensions.readthedocs.io/
diff --git a/contrib/python/typing-extensions/py3/typing_extensions.py b/contrib/python/typing-extensions/py3/typing_extensions.py
index dec429ca872..fa89c83efcd 100644
--- a/contrib/python/typing-extensions/py3/typing_extensions.py
+++ b/contrib/python/typing-extensions/py3/typing_extensions.py
@@ -1,9 +1,12 @@
import abc
+import builtins
import collections
import collections.abc
import contextlib
+import enum
import functools
import inspect
+import keyword
import operator
import sys
import types as _types
@@ -62,8 +65,11 @@ __all__ = [
'dataclass_transform',
'deprecated',
'Doc',
+ 'evaluate_forward_ref',
'get_overloads',
'final',
+ 'Format',
+ 'get_annotations',
'get_args',
'get_origin',
'get_original_bases',
@@ -83,6 +89,7 @@ __all__ = [
'Text',
'TypeAlias',
'TypeAliasType',
+ 'TypeForm',
'TypeGuard',
'TypeIs',
'TYPE_CHECKING',
@@ -91,6 +98,8 @@ __all__ = [
'ReadOnly',
'Required',
'NotRequired',
+ 'NoDefault',
+ 'NoExtraItems',
# Pure aliases, have always been in typing
'AbstractSet',
@@ -117,7 +126,6 @@ __all__ = [
'MutableMapping',
'MutableSequence',
'MutableSet',
- 'NoDefault',
'Optional',
'Pattern',
'Reversible',
@@ -138,6 +146,9 @@ PEP_560 = True
GenericMeta = type
_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta")
+# Added with bpo-45166 to 3.10.1+ and some 3.9 versions
+_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__
+
# The functions below are modified copies of typing internal helpers.
# They are needed by _ProtocolMeta and they provide support for PEP 646.
@@ -867,6 +878,63 @@ def _ensure_subclassable(mro_entries):
return inner
+_NEEDS_SINGLETONMETA = (
+ not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
+)
+
+if _NEEDS_SINGLETONMETA:
+ class SingletonMeta(type):
+ def __setattr__(cls, attr, value):
+ # TypeError is consistent with the behavior of NoneType
+ raise TypeError(
+ f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
+ )
+
+
+if hasattr(typing, "NoDefault"):
+ NoDefault = typing.NoDefault
+else:
+ class NoDefaultType(metaclass=SingletonMeta):
+ """The type of the NoDefault singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoDefault") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoDefault"
+
+ def __reduce__(self):
+ return "NoDefault"
+
+ NoDefault = NoDefaultType()
+ del NoDefaultType
+
+if hasattr(typing, "NoExtraItems"):
+ NoExtraItems = typing.NoExtraItems
+else:
+ class NoExtraItemsType(metaclass=SingletonMeta):
+ """The type of the NoExtraItems singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoExtraItems") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoExtraItems"
+
+ def __reduce__(self):
+ return "NoExtraItems"
+
+ NoExtraItems = NoExtraItemsType()
+ del NoExtraItemsType
+
+if _NEEDS_SINGLETONMETA:
+ del SingletonMeta
+
+
# Update this to something like >=3.13.0b1 if and when
# PEP 728 is implemented in CPython
_PEP_728_IMPLEMENTED = False
@@ -913,7 +981,9 @@ else:
break
class _TypedDictMeta(type):
- def __new__(cls, name, bases, ns, *, total=True, closed=False):
+
+ def __new__(cls, name, bases, ns, *, total=True, closed=None,
+ extra_items=NoExtraItems):
"""Create new typed dict class object.
This method is called when TypedDict is subclassed,
@@ -925,6 +995,8 @@ else:
if type(base) is not _TypedDictMeta and base is not typing.Generic:
raise TypeError('cannot inherit from both a TypedDict type '
'and a non-TypedDict base class')
+ if closed is not None and extra_items is not NoExtraItems:
+ raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
if any(issubclass(b, typing.Generic) for b in bases):
generic_base = (typing.Generic,)
@@ -964,7 +1036,7 @@ else:
optional_keys = set()
readonly_keys = set()
mutable_keys = set()
- extra_items_type = None
+ extra_items_type = extra_items
for base in bases:
base_dict = base.__dict__
@@ -974,13 +1046,12 @@ else:
optional_keys.update(base_dict.get('__optional_keys__', ()))
readonly_keys.update(base_dict.get('__readonly_keys__', ()))
mutable_keys.update(base_dict.get('__mutable_keys__', ()))
- base_extra_items_type = base_dict.get('__extra_items__', None)
- if base_extra_items_type is not None:
- extra_items_type = base_extra_items_type
- if closed and extra_items_type is None:
- extra_items_type = Never
- if closed and "__extra_items__" in own_annotations:
+ # This was specified in an earlier version of PEP 728. Support
+ # is retained for backwards compatibility, but only for Python
+ # 3.13 and lower.
+ if (closed and sys.version_info < (3, 14)
+ and "__extra_items__" in own_annotations):
annotation_type = own_annotations.pop("__extra_items__")
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
if Required in qualifiers:
@@ -1019,8 +1090,7 @@ else:
tp_dict.__optional_keys__ = frozenset(optional_keys)
tp_dict.__readonly_keys__ = frozenset(readonly_keys)
tp_dict.__mutable_keys__ = frozenset(mutable_keys)
- if not hasattr(tp_dict, '__total__'):
- tp_dict.__total__ = total
+ tp_dict.__total__ = total
tp_dict.__closed__ = closed
tp_dict.__extra_items__ = extra_items_type
return tp_dict
@@ -1036,7 +1106,16 @@ else:
_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
@_ensure_subclassable(lambda bases: (_TypedDict,))
- def TypedDict(typename, fields=_marker, /, *, total=True, closed=False, **kwargs):
+ def TypedDict(
+ typename,
+ fields=_marker,
+ /,
+ *,
+ total=True,
+ closed=None,
+ extra_items=NoExtraItems,
+ **kwargs
+ ):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
@@ -1096,9 +1175,14 @@ else:
"using the functional syntax, pass an empty dictionary, e.g. "
) + example + "."
warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
- if closed is not False and closed is not True:
+ # Support a field called "closed"
+ if closed is not False and closed is not True and closed is not None:
kwargs["closed"] = closed
- closed = False
+ closed = None
+ # Or "extra_items"
+ if extra_items is not NoExtraItems:
+ kwargs["extra_items"] = extra_items
+ extra_items = NoExtraItems
fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
@@ -1120,7 +1204,8 @@ else:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
- td = _TypedDictMeta(typename, (), ns, total=total, closed=closed)
+ td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
+ extra_items=extra_items)
td.__orig_bases__ = (TypedDict,)
return td
@@ -1232,10 +1317,90 @@ else: # <=3.13
)
else: # 3.8
hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
+ if sys.version_info < (3, 11):
+ _clean_optional(obj, hint, globalns, localns)
+ if sys.version_info < (3, 9):
+ # In 3.8 eval_type does not flatten Optional[ForwardRef] correctly
+ # This will recreate and and cache Unions.
+ hint = {
+ k: (t
+ if get_origin(t) != Union
+ else Union[t.__args__])
+ for k, t in hint.items()
+ }
if include_extras:
return hint
return {k: _strip_extras(t) for k, t in hint.items()}
+ _NoneType = type(None)
+
+ def _could_be_inserted_optional(t):
+ """detects Union[..., None] pattern"""
+ # 3.8+ compatible checking before _UnionGenericAlias
+ if get_origin(t) is not Union:
+ return False
+ # Assume if last argument is not None they are user defined
+ if t.__args__[-1] is not _NoneType:
+ return False
+ return True
+
+ # < 3.11
+ def _clean_optional(obj, hints, globalns=None, localns=None):
+ # reverts injected Union[..., None] cases from typing.get_type_hints
+ # when a None default value is used.
+ # see https://github.com/python/typing_extensions/issues/310
+ if not hints or isinstance(obj, type):
+ return
+ defaults = typing._get_defaults(obj) # avoid accessing __annotations___
+ if not defaults:
+ return
+ original_hints = obj.__annotations__
+ for name, value in hints.items():
+ # Not a Union[..., None] or replacement conditions not fullfilled
+ if (not _could_be_inserted_optional(value)
+ or name not in defaults
+ or defaults[name] is not None
+ ):
+ continue
+ original_value = original_hints[name]
+ # value=NoneType should have caused a skip above but check for safety
+ if original_value is None:
+ original_value = _NoneType
+ # Forward reference
+ if isinstance(original_value, str):
+ if globalns is None:
+ if isinstance(obj, _types.ModuleType):
+ globalns = obj.__dict__
+ else:
+ nsobj = obj
+ # Find globalns for the unwrapped object.
+ while hasattr(nsobj, '__wrapped__'):
+ nsobj = nsobj.__wrapped__
+ globalns = getattr(nsobj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
+ localns = globalns
+ if sys.version_info < (3, 9):
+ original_value = ForwardRef(original_value)
+ else:
+ original_value = ForwardRef(
+ original_value,
+ is_argument=not isinstance(obj, _types.ModuleType)
+ )
+ original_evaluated = typing._eval_type(original_value, globalns, localns)
+ if sys.version_info < (3, 9) and get_origin(original_evaluated) is Union:
+ # Union[str, None, "str"] is not reduced to Union[str, None]
+ original_evaluated = Union[original_evaluated.__args__]
+ # Compare if values differ. Note that even if equal
+ # value might be cached by typing._tp_cache contrary to original_evaluated
+ if original_evaluated != value or (
+ # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias
+ hasattr(_types, "UnionType")
+ and isinstance(original_evaluated, _types.UnionType)
+ and not isinstance(value, _types.UnionType)
+ ):
+ hints[name] = original_evaluated
# Python 3.9+ has PEP 593 (Annotated)
if hasattr(typing, 'Annotated'):
@@ -1443,34 +1608,6 @@ else:
)
-if hasattr(typing, "NoDefault"):
- NoDefault = typing.NoDefault
-else:
- class NoDefaultTypeMeta(type):
- def __setattr__(cls, attr, value):
- # TypeError is consistent with the behavior of NoneType
- raise TypeError(
- f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
- )
-
- class NoDefaultType(metaclass=NoDefaultTypeMeta):
- """The type of the NoDefault singleton."""
-
- __slots__ = ()
-
- def __new__(cls):
- return globals().get("NoDefault") or object.__new__(cls)
-
- def __repr__(self):
- return "typing_extensions.NoDefault"
-
- def __reduce__(self):
- return "NoDefault"
-
- NoDefault = NoDefaultType()
- del NoDefaultType, NoDefaultTypeMeta
-
-
def _set_default(type_param, default):
type_param.has_default = lambda: default is not NoDefault
type_param.__default__ = default
@@ -1761,6 +1898,23 @@ else:
# 3.8-3.9
if not hasattr(typing, 'Concatenate'):
# Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+
+ # 3.9.0-1
+ if not hasattr(typing, '_type_convert'):
+ def _type_convert(arg, module=None, *, allow_special_forms=False):
+ """For converting None to type(None), and strings to ForwardRef."""
+ if arg is None:
+ return type(None)
+ if isinstance(arg, str):
+ if sys.version_info <= (3, 9, 6):
+ return ForwardRef(arg)
+ if sys.version_info <= (3, 9, 7):
+ return ForwardRef(arg, module=module)
+ return ForwardRef(arg, module=module, is_class=allow_special_forms)
+ return arg
+ else:
+ _type_convert = typing._type_convert
+
class _ConcatenateGenericAlias(list):
# Trick Generic into looking into this for __parameters__.
@@ -1792,27 +1946,171 @@ if not hasattr(typing, 'Concatenate'):
tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
)
+ # 3.8; needed for typing._subst_tvars
+ # 3.9 used by __getitem__ below
+ def copy_with(self, params):
+ if isinstance(params[-1], _ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return self.__class__(self.__origin__, params)
+
+ # 3.9; accessed during GenericAlias.__getitem__ when substituting
+ def __getitem__(self, args):
+ if self.__origin__ in (Generic, Protocol):
+ # Can't subscript Generic[...] or Protocol[...].
+ raise TypeError(f"Cannot subscript already-subscripted {self}")
+ if not self.__parameters__:
+ raise TypeError(f"{self} is not a generic class")
-# 3.8-3.9
+ if not isinstance(args, tuple):
+ args = (args,)
+ args = _unpack_args(*(_type_convert(p) for p in args))
+ params = self.__parameters__
+ for param in params:
+ prepare = getattr(param, "__typing_prepare_subst__", None)
+ if prepare is not None:
+ args = prepare(self, args)
+ # 3.8 - 3.9 & typing.ParamSpec
+ elif isinstance(param, ParamSpec):
+ i = params.index(param)
+ if (
+ i == len(args)
+ and getattr(param, '__default__', NoDefault) is not NoDefault
+ ):
+ args = [*args, param.__default__]
+ if i >= len(args):
+ raise TypeError(f"Too few arguments for {self}")
+ # Special case for Z[[int, str, bool]] == Z[int, str, bool]
+ if len(params) == 1 and not _is_param_expr(args[0]):
+ assert i == 0
+ args = (args,)
+ elif (
+ isinstance(args[i], list)
+ # 3.8 - 3.9
+ # This class inherits from list do not convert
+ and not isinstance(args[i], _ConcatenateGenericAlias)
+ ):
+ args = (*args[:i], tuple(args[i]), *args[i + 1:])
+
+ alen = len(args)
+ plen = len(params)
+ if alen != plen:
+ raise TypeError(
+ f"Too {'many' if alen > plen else 'few'} arguments for {self};"
+ f" actual {alen}, expected {plen}"
+ )
+
+ subst = dict(zip(self.__parameters__, args))
+ # determine new args
+ new_args = []
+ for arg in self.__args__:
+ if isinstance(arg, type):
+ new_args.append(arg)
+ continue
+ if isinstance(arg, TypeVar):
+ arg = subst[arg]
+ if (
+ (isinstance(arg, typing._GenericAlias) and _is_unpack(arg))
+ or (
+ hasattr(_types, "GenericAlias")
+ and isinstance(arg, _types.GenericAlias)
+ and getattr(arg, "__unpacked__", False)
+ )
+ ):
+ raise TypeError(f"{arg} is not valid as type argument")
+
+ elif isinstance(arg,
+ typing._GenericAlias
+ if not hasattr(_types, "GenericAlias") else
+ (typing._GenericAlias, _types.GenericAlias)
+ ):
+ subparams = arg.__parameters__
+ if subparams:
+ subargs = tuple(subst[x] for x in subparams)
+ arg = arg[subargs]
+ new_args.append(arg)
+ return self.copy_with(tuple(new_args))
+
+# 3.10+
+else:
+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
+
+ # 3.10
+ if sys.version_info < (3, 11):
+
+ class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True):
+ # needed for checks in collections.abc.Callable to accept this class
+ __module__ = "typing"
+
+ def copy_with(self, params):
+ if isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ if isinstance(params[-1], typing._ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return super(typing._ConcatenateGenericAlias, self).copy_with(params)
+
+ def __getitem__(self, args):
+ value = super().__getitem__(args)
+ if isinstance(value, tuple) and any(_is_unpack(t) for t in value):
+ return tuple(_unpack_args(*(n for n in value)))
+ return value
+
+
+# 3.8-3.9.2
+class _EllipsisDummy: ...
+
+
+# 3.8-3.10
+def _create_concatenate_alias(origin, parameters):
+ if parameters[-1] is ... and sys.version_info < (3, 9, 2):
+ # Hack: Arguments must be types, replace it with one.
+ parameters = (*parameters[:-1], _EllipsisDummy)
+ if sys.version_info >= (3, 10, 3):
+ concatenate = _ConcatenateGenericAlias(origin, parameters,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
+ else:
+ concatenate = _ConcatenateGenericAlias(origin, parameters)
+ if parameters[-1] is not _EllipsisDummy:
+ return concatenate
+ # Remove dummy again
+ concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
+ for p in concatenate.__args__)
+ if sys.version_info < (3, 10):
+ # backport needs __args__ adjustment only
+ return concatenate
+ concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
+ if p is not _EllipsisDummy)
+ return concatenate
+
+
+# 3.8-3.10
@typing._tp_cache
def _concatenate_getitem(self, parameters):
if parameters == ():
raise TypeError("Cannot take a Concatenate of no types.")
if not isinstance(parameters, tuple):
parameters = (parameters,)
- if not isinstance(parameters[-1], ParamSpec):
+ if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
raise TypeError("The last parameter to Concatenate should be a "
- "ParamSpec variable.")
+ "ParamSpec variable or ellipsis.")
msg = "Concatenate[arg, ...]: each arg must be a type."
- parameters = tuple(typing._type_check(p, msg) for p in parameters)
- return _ConcatenateGenericAlias(self, parameters)
+ parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
+ parameters[-1])
+ return _create_concatenate_alias(self, parameters)
-# 3.10+
-if hasattr(typing, 'Concatenate'):
+# 3.11+; Concatenate does not accept ellipsis in 3.10
+if sys.version_info >= (3, 11):
Concatenate = typing.Concatenate
- _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
-# 3.9
+# 3.9-3.10
elif sys.version_info[:2] >= (3, 9):
@_ExtensionsSpecialForm
def Concatenate(self, parameters):
@@ -1976,7 +2274,7 @@ elif sys.version_info[:2] >= (3, 9):
1. The return value is a boolean.
2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeGuard`` and the argument's
+ is the intersection of the type inside ``TypeIs`` and the argument's
previously known type.
For example::
@@ -2024,7 +2322,7 @@ else:
1. The return value is a boolean.
2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeGuard`` and the argument's
+ is the intersection of the type inside ``TypeIs`` and the argument's
previously known type.
For example::
@@ -2042,6 +2340,69 @@ else:
PEP 742 (Narrowing types with TypeIs).
""")
+# 3.14+?
+if hasattr(typing, 'TypeForm'):
+ TypeForm = typing.TypeForm
+# 3.9
+elif sys.version_info[:2] >= (3, 9):
+ class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+ # TypeForm(X) is equivalent to X but indicates to the type checker
+ # that the object is a TypeForm.
+ def __call__(self, obj, /):
+ return obj
+
+ @_TypeFormForm
+ def TypeForm(self, parameters):
+ """A special form representing the value that results from the evaluation
+ of a type expression. This value encodes the information supplied in the
+ type expression, and it represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form objects.
+ It accepts a single type argument, which must be a valid type expression.
+ ``TypeForm[T]`` describes the set of all type form objects that represent
+ the type T or types that are assignable to T.
+
+ Usage:
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+# 3.8
+else:
+ class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type')
+ return typing._GenericAlias(self, (item,))
+
+ def __call__(self, obj, /):
+ return obj
+
+ TypeForm = _TypeFormForm(
+ 'TypeForm',
+ doc="""A special form representing the value that results from the evaluation
+ of a type expression. This value encodes the information supplied in the
+ type expression, and it represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form objects.
+ It accepts a single type argument, which must be a valid type expression.
+ ``TypeForm[T]`` describes the set of all type form objects that represent
+ the type T or types that are assignable to T.
+
+ Usage:
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """)
+
# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
@@ -2344,7 +2705,9 @@ elif sys.version_info[:2] >= (3, 9): # 3.9+
self.__doc__ = _UNPACK_DOC
class _UnpackAlias(typing._GenericAlias, _root=True):
- __class__ = typing.TypeVar
+ if sys.version_info < (3, 11):
+ # needed for compatibility with Generic[Unpack[Ts]]
+ __class__ = typing.TypeVar
@property
def __typing_unpacked_tuple_args__(self):
@@ -2357,6 +2720,17 @@ elif sys.version_info[:2] >= (3, 9): # 3.9+
return arg.__args__
return None
+ @property
+ def __typing_is_unpacked_typevartuple__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ return isinstance(self.__args__[0], TypeVarTuple)
+
+ def __getitem__(self, args):
+ if self.__typing_is_unpacked_typevartuple__:
+ return args
+ return super().__getitem__(args)
+
@_UnpackSpecialForm
def Unpack(self, parameters):
item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
@@ -2369,6 +2743,28 @@ else: # 3.8
class _UnpackAlias(typing._GenericAlias, _root=True):
__class__ = typing.TypeVar
+ @property
+ def __typing_unpacked_tuple_args__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ arg, = self.__args__
+ if isinstance(arg, typing._GenericAlias):
+ if arg.__origin__ is not tuple:
+ raise TypeError("Unpack[...] must be used with a tuple type")
+ return arg.__args__
+ return None
+
+ @property
+ def __typing_is_unpacked_typevartuple__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ return isinstance(self.__args__[0], TypeVarTuple)
+
+ def __getitem__(self, args):
+ if self.__typing_is_unpacked_typevartuple__:
+ return args
+ return super().__getitem__(args)
+
class _UnpackForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
@@ -2381,21 +2777,22 @@ else: # 3.8
return isinstance(obj, _UnpackAlias)
+def _unpack_args(*args):
+ newargs = []
+ for arg in args:
+ subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+ if subargs is not None and (not (subargs and subargs[-1] is ...)):
+ newargs.extend(subargs)
+ else:
+ newargs.append(arg)
+ return newargs
+
+
if _PEP_696_IMPLEMENTED:
from typing import TypeVarTuple
elif hasattr(typing, "TypeVarTuple"): # 3.11+
- def _unpack_args(*args):
- newargs = []
- for arg in args:
- subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
- if subargs is not None and not (subargs and subargs[-1] is ...):
- newargs.extend(subargs)
- else:
- newargs.append(arg)
- return newargs
-
# Add default parameter - PEP 696
class TypeVarTuple(metaclass=_TypeVarLikeMeta):
"""Type variable tuple."""
@@ -2726,7 +3123,8 @@ else: # <=3.11
return arg
-if hasattr(warnings, "deprecated"):
+# Python 3.13.3+ contains a fix for the wrapped __new__
+if sys.version_info >= (3, 13, 3):
deprecated = warnings.deprecated
else:
_T = typing.TypeVar("_T")
@@ -2806,7 +3204,7 @@ else:
original_new = arg.__new__
@functools.wraps(original_new)
- def __new__(cls, *args, **kwargs):
+ def __new__(cls, /, *args, **kwargs):
if cls is arg:
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
if original_new is not object.__new__:
@@ -2845,13 +3243,21 @@ else:
__init_subclass__.__deprecated__ = msg
return arg
elif callable(arg):
+ import asyncio.coroutines
import functools
+ import inspect
@functools.wraps(arg)
def wrapper(*args, **kwargs):
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
return arg(*args, **kwargs)
+ if asyncio.coroutines.iscoroutinefunction(arg):
+ if sys.version_info >= (3, 12):
+ wrapper = inspect.markcoroutinefunction(wrapper)
+ else:
+ wrapper._is_coroutine = asyncio.coroutines._is_coroutine
+
arg.__deprecated__ = wrapper.__deprecated__ = msg
return wrapper
else:
@@ -2860,6 +3266,24 @@ else:
f"a class or callable, not {arg!r}"
)
+if sys.version_info < (3, 10):
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias)
+ )
+else:
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg,
+ (
+ tuple,
+ list,
+ ParamSpec,
+ _ConcatenateGenericAlias,
+ typing._ConcatenateGenericAlias,
+ ),
+ )
+
# We have to do some monkey patching to deal with the dual nature of
# Unpack/TypeVarTuple:
@@ -2874,6 +3298,17 @@ if not hasattr(typing, "TypeVarTuple"):
This gives a nice error message in case of count mismatch.
"""
+ # If substituting a single ParamSpec with multiple arguments
+ # we do not check the count
+ if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
+ and len(cls.__parameters__) == 1
+ and isinstance(cls.__parameters__[0], ParamSpec)
+ and parameters
+ and not _is_param_expr(parameters[0])
+ ):
+ # Generic modifies parameters variable, but here we cannot do this
+ return
+
if not elen:
raise TypeError(f"{cls} is not a generic class")
if elen is _marker:
@@ -3007,7 +3442,10 @@ if hasattr(typing, '_collect_type_vars'):
for t in types:
if _is_unpacked_typevartuple(t):
type_var_tuple_encountered = True
- elif isinstance(t, typevar_types) and t not in tvars:
+ elif (
+ isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias)
+ and t not in tvars
+ ):
if enforce_default_ordering:
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
if has_default:
@@ -3022,6 +3460,13 @@ if hasattr(typing, '_collect_type_vars'):
tvars.append(t)
if _should_collect_from_parameters(t):
tvars.extend([t for t in t.__parameters__ if t not in tvars])
+ elif isinstance(t, tuple):
+ # Collect nested type_vars
+ # tuple wrapped by _prepare_paramspec_params(cls, params)
+ for x in t:
+ for collected in _collect_type_vars([x]):
+ if collected not in tvars:
+ tvars.append(collected)
return tuple(tvars)
typing._collect_type_vars = _collect_type_vars
@@ -3379,17 +3824,62 @@ else:
return typing.Union[other, self]
-if hasattr(typing, "TypeAliasType"):
+if sys.version_info >= (3, 14):
TypeAliasType = typing.TypeAliasType
+# 3.8-3.13
else:
- def _is_unionable(obj):
- """Corresponds to is_unionable() in unionobject.c in CPython."""
- return obj is None or isinstance(obj, (
- type,
- _types.GenericAlias,
- _types.UnionType,
- TypeAliasType,
- ))
+ if sys.version_info >= (3, 12):
+ # 3.12-3.14
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ typing.TypeAliasType,
+ TypeAliasType,
+ ))
+ else:
+ # 3.8-3.11
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ TypeAliasType,
+ ))
+
+ if sys.version_info < (3, 10):
+ # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
+ # so that we emulate the behaviour of `types.GenericAlias`
+ # on the latest versions of CPython
+ _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
+ "__class__",
+ "__bases__",
+ "__origin__",
+ "__args__",
+ "__unpacked__",
+ "__parameters__",
+ "__typing_unpacked_tuple_args__",
+ "__mro_entries__",
+ "__reduce_ex__",
+ "__reduce__",
+ "__copy__",
+ "__deepcopy__",
+ })
+
+ class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
+ def __getattr__(self, attr):
+ if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
+ return object.__getattr__(self, attr)
+ return getattr(self.__origin__, attr)
+
+ if sys.version_info < (3, 9):
+ def __getitem__(self, item):
+ result = super().__getitem__(item)
+ result.__class__ = type(self)
+ return result
class TypeAliasType:
"""Create named, parameterized type aliases.
@@ -3422,11 +3912,29 @@ else:
def __init__(self, name: str, value, *, type_params=()):
if not isinstance(name, str):
raise TypeError("TypeAliasType name must be a string")
+ if not isinstance(type_params, tuple):
+ raise TypeError("type_params must be a tuple")
self.__value__ = value
self.__type_params__ = type_params
+ default_value_encountered = False
parameters = []
for type_param in type_params:
+ if (
+ not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
+ # 3.8-3.11
+ # Unpack Backport passes isinstance(type_param, TypeVar)
+ or _is_unpack(type_param)
+ ):
+ raise TypeError(f"Expected a type param, got {type_param!r}")
+ has_default = (
+ getattr(type_param, '__default__', NoDefault) is not NoDefault
+ )
+ if default_value_encountered and not has_default:
+ raise TypeError(f"non-default type parameter '{type_param!r}'"
+ " follows default type parameter")
+ if has_default:
+ default_value_encountered = True
if isinstance(type_param, TypeVarTuple):
parameters.extend(type_param)
else:
@@ -3463,16 +3971,49 @@ else:
def __repr__(self) -> str:
return self.__name__
+ if sys.version_info < (3, 11):
+ def _check_single_param(self, param, recursion=0):
+ # Allow [], [int], [int, str], [int, ...], [int, T]
+ if param is ...:
+ return ...
+ if param is None:
+ return None
+ # Note in <= 3.9 _ConcatenateGenericAlias inherits from list
+ if isinstance(param, list) and recursion == 0:
+ return [self._check_single_param(arg, recursion+1)
+ for arg in param]
+ return typing._type_check(
+ param, f'Subscripting {self.__name__} requires a type.'
+ )
+
+ def _check_parameters(self, parameters):
+ if sys.version_info < (3, 11):
+ return tuple(
+ self._check_single_param(item)
+ for item in parameters
+ )
+ return tuple(typing._type_check(
+ item, f'Subscripting {self.__name__} requires a type.'
+ )
+ for item in parameters
+ )
+
def __getitem__(self, parameters):
+ if not self.__type_params__:
+ raise TypeError("Only generic type aliases are subscriptable")
if not isinstance(parameters, tuple):
parameters = (parameters,)
- parameters = [
- typing._type_check(
- item, f'Subscripting {self.__name__} requires a type.'
- )
- for item in parameters
- ]
- return typing._GenericAlias(self, tuple(parameters))
+ # Using 3.9 here will create problems with Concatenate
+ if sys.version_info >= (3, 10):
+ return _types.GenericAlias(self, parameters)
+ type_vars = _collect_type_vars(parameters)
+ parameters = self._check_parameters(parameters)
+ alias = _TypeAliasGenericAlias(self, parameters)
+ # alias.__parameters__ is not complete if Concatenate is present
+ # as it is converted to a list from which no parameters are extracted.
+ if alias.__parameters__ != type_vars:
+ alias.__parameters__ = type_vars
+ return alias
def __reduce__(self):
return self.__name__
@@ -3599,6 +4140,408 @@ if _CapsuleType is not None:
__all__.append("CapsuleType")
+# Using this convoluted approach so that this keeps working
+# whether we end up using PEP 649 as written, PEP 749, or
+# some other variation: in any case, inspect.get_annotations
+# will continue to exist and will gain a `format` parameter.
+_PEP_649_OR_749_IMPLEMENTED = (
+ hasattr(inspect, 'get_annotations')
+ and inspect.get_annotations.__kwdefaults__ is not None
+ and "format" in inspect.get_annotations.__kwdefaults__
+)
+
+
+class Format(enum.IntEnum):
+ VALUE = 1
+ FORWARDREF = 2
+ STRING = 3
+
+
+if _PEP_649_OR_749_IMPLEMENTED:
+ get_annotations = inspect.get_annotations
+else:
+ def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
+ format=Format.VALUE):
+ """Compute the annotations dict for an object.
+
+ obj may be a callable, class, or module.
+ Passing in an object of any other type raises TypeError.
+
+ Returns a dict. get_annotations() returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This is a backport of `inspect.get_annotations`, which has been
+ in the standard library since Python 3.10. See the standard library
+ documentation for more:
+
+ https://docs.python.org/3/library/inspect.html#inspect.get_annotations
+
+ This backport adds the *format* argument introduced by PEP 649. The
+ three formats supported are:
+ * VALUE: the annotations are returned as-is. This is the default and
+ it is compatible with the behavior on previous Python versions.
+ * FORWARDREF: return annotations as-is if possible, but replace any
+ undefined names with ForwardRef objects. The implementation proposed by
+ PEP 649 relies on language changes that cannot be backported; the
+ typing-extensions implementation simply returns the same result as VALUE.
+ * STRING: return annotations as strings, in a format close to the original
+ source. Again, this behavior cannot be replicated directly in a backport.
+ As an approximation, typing-extensions retrieves the annotations under
+ VALUE semantics and then stringifies them.
+
+ The purpose of this backport is to allow users who would like to use
+ FORWARDREF or STRING semantics once PEP 649 is implemented, but who also
+ want to support earlier Python versions, to simply write:
+
+ typing_extensions.get_annotations(obj, format=Format.FORWARDREF)
+
+ """
+ format = Format(format)
+
+ if eval_str and format is not Format.VALUE:
+ raise ValueError("eval_str=True is only supported with format=Format.VALUE")
+
+ if isinstance(obj, type):
+ # class
+ obj_dict = getattr(obj, '__dict__', None)
+ if obj_dict and hasattr(obj_dict, 'get'):
+ ann = obj_dict.get('__annotations__', None)
+ if isinstance(ann, _types.GetSetDescriptorType):
+ ann = None
+ else:
+ ann = None
+
+ obj_globals = None
+ module_name = getattr(obj, '__module__', None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, '__dict__', None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, _types.ModuleType):
+ # module
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = obj.__dict__
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__globals__', None)
+ obj_locals = None
+ unwrap = obj
+ elif hasattr(obj, '__annotations__'):
+ ann = obj.__annotations__
+ obj_globals = obj_locals = unwrap = None
+ else:
+ raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+ if ann is None:
+ return {}
+
+ if not isinstance(ann, dict):
+ raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+ if not ann:
+ return {}
+
+ if not eval_str:
+ if format is Format.STRING:
+ return {
+ key: value if isinstance(value, str) else typing._type_repr(value)
+ for key, value in ann.items()
+ }
+ return dict(ann)
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, '__wrapped__'):
+ unwrap = unwrap.__wrapped__
+ continue
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
+
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals or {}
+
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ if type_params := getattr(obj, "__type_params__", ()):
+ locals = {param.__name__: param for param in type_params} | locals
+
+ return_value = {key:
+ value if not isinstance(value, str) else eval(value, globals, locals)
+ for key, value in ann.items() }
+ return return_value
+
+
+if hasattr(typing, "evaluate_forward_ref"):
+ evaluate_forward_ref = typing.evaluate_forward_ref
+else:
+ # Implements annotationlib.ForwardRef.evaluate
+ def _eval_with_owner(
+ forward_ref, *, owner=None, globals=None, locals=None, type_params=None
+ ):
+ if forward_ref.__forward_evaluated__:
+ return forward_ref.__forward_value__
+ if getattr(forward_ref, "__cell__", None) is not None:
+ try:
+ value = forward_ref.__cell__.cell_contents
+ except ValueError:
+ pass
+ else:
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+ if owner is None:
+ owner = getattr(forward_ref, "__owner__", None)
+
+ if (
+ globals is None
+ and getattr(forward_ref, "__forward_module__", None) is not None
+ ):
+ globals = getattr(
+ sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None
+ )
+ if globals is None:
+ globals = getattr(forward_ref, "__globals__", None)
+ if globals is None:
+ if isinstance(owner, type):
+ module_name = getattr(owner, "__module__", None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ globals = getattr(module, "__dict__", None)
+ elif isinstance(owner, _types.ModuleType):
+ globals = getattr(owner, "__dict__", None)
+ elif callable(owner):
+ globals = getattr(owner, "__globals__", None)
+
+ # If we pass None to eval() below, the globals of this module are used.
+ if globals is None:
+ globals = {}
+
+ if locals is None:
+ locals = {}
+ if isinstance(owner, type):
+ locals.update(vars(owner))
+
+ if type_params is None and owner is not None:
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ type_params = getattr(owner, "__type_params__", None)
+
+ # type parameters require some special handling,
+ # as they exist in their own scope
+ # but `eval()` does not have a dedicated parameter for that scope.
+ # For classes, names in type parameter scopes should override
+ # names in the global scope (which here are called `localns`!),
+ # but should in turn be overridden by names in the class scope
+ # (which here are called `globalns`!)
+ if type_params is not None:
+ globals = dict(globals)
+ locals = dict(locals)
+ for param in type_params:
+ param_name = param.__name__
+ if (
+ _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__
+ ) or param_name not in globals:
+ globals[param_name] = param
+ locals.pop(param_name, None)
+
+ arg = forward_ref.__forward_arg__
+ if arg.isidentifier() and not keyword.iskeyword(arg):
+ if arg in locals:
+ value = locals[arg]
+ elif arg in globals:
+ value = globals[arg]
+ elif hasattr(builtins, arg):
+ return getattr(builtins, arg)
+ else:
+ raise NameError(arg)
+ else:
+ code = forward_ref.__forward_code__
+ value = eval(code, globals, locals)
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+
+ def _lax_type_check(
+ value, msg, is_argument=True, *, module=None, allow_special_forms=False
+ ):
+ """
+ A lax Python 3.11+ like version of typing._type_check
+ """
+ if hasattr(typing, "_type_convert"):
+ if (
+ sys.version_info >= (3, 10, 3)
+ or (3, 9, 10) < sys.version_info[:3] < (3, 10)
+ ):
+ # allow_special_forms introduced later cpython/#30926 (bpo-46539)
+ type_ = typing._type_convert(
+ value,
+ module=module,
+ allow_special_forms=allow_special_forms,
+ )
+ # module was added with bpo-41249 before is_class (bpo-46539)
+ elif "__forward_module__" in typing.ForwardRef.__slots__:
+ type_ = typing._type_convert(value, module=module)
+ else:
+ type_ = typing._type_convert(value)
+ else:
+ if value is None:
+ return type(None)
+ if isinstance(value, str):
+ return ForwardRef(value)
+ type_ = value
+ invalid_generic_forms = (Generic, Protocol)
+ if not allow_special_forms:
+ invalid_generic_forms += (ClassVar,)
+ if is_argument:
+ invalid_generic_forms += (Final,)
+ if (
+ isinstance(type_, typing._GenericAlias)
+ and get_origin(type_) in invalid_generic_forms
+ ):
+ raise TypeError(f"{type_} is not valid as type argument") from None
+ if type_ in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
+ return type_
+ if allow_special_forms and type_ in (ClassVar, Final):
+ return type_
+ if (
+ isinstance(type_, (_SpecialForm, typing._SpecialForm))
+ or type_ in (Generic, Protocol)
+ ):
+ raise TypeError(f"Plain {type_} is not valid as type argument") from None
+ if type(type_) is tuple: # lax version with tuple instead of callable
+ raise TypeError(f"{msg} Got {type_!r:.100}.")
+ return type_
+
+ def evaluate_forward_ref(
+ forward_ref,
+ *,
+ owner=None,
+ globals=None,
+ locals=None,
+ type_params=None,
+ format=Format.VALUE,
+ _recursive_guard=frozenset(),
+ ):
+ """Evaluate a forward reference as a type hint.
+
+ This is similar to calling the ForwardRef.evaluate() method,
+ but unlike that method, evaluate_forward_ref() also:
+
+ * Recursively evaluates forward references nested within the type hint.
+ * Rejects certain objects that are not valid type hints.
+ * Replaces type hints that evaluate to None with types.NoneType.
+ * Supports the *FORWARDREF* and *STRING* formats.
+
+ *forward_ref* must be an instance of ForwardRef. *owner*, if given,
+ should be the object that holds the annotations that the forward reference
+ derived from, such as a module, class object, or function. It is used to
+ infer the namespaces to use for looking up names. *globals* and *locals*
+ can also be explicitly given to provide the global and local namespaces.
+ *type_params* is a tuple of type parameters that are in scope when
+ evaluating the forward reference. This parameter must be provided (though
+ it may be an empty tuple) if *owner* is not given and the forward reference
+ does not already have an owner set. *format* specifies the format of the
+ annotation and is a member of the annotationlib.Format enum.
+
+ """
+ if format == Format.STRING:
+ return forward_ref.__forward_arg__
+ if forward_ref.__forward_arg__ in _recursive_guard:
+ return forward_ref
+
+ # Evaluate the forward reference
+ try:
+ value = _eval_with_owner(
+ forward_ref,
+ owner=owner,
+ globals=globals,
+ locals=locals,
+ type_params=type_params,
+ )
+ except NameError:
+ if format == Format.FORWARDREF:
+ return forward_ref
+ else:
+ raise
+
+ msg = "Forward references must evaluate to types."
+ if not _FORWARD_REF_HAS_CLASS:
+ allow_special_forms = not forward_ref.__forward_is_argument__
+ else:
+ allow_special_forms = forward_ref.__forward_is_class__
+ type_ = _lax_type_check(
+ value,
+ msg,
+ is_argument=forward_ref.__forward_is_argument__,
+ allow_special_forms=allow_special_forms,
+ )
+
+ # Recursively evaluate the type
+ if isinstance(type_, ForwardRef):
+ if getattr(type_, "__forward_module__", True) is not None:
+ globals = None
+ return evaluate_forward_ref(
+ type_,
+ globals=globals,
+ locals=locals,
+ type_params=type_params, owner=owner,
+ _recursive_guard=_recursive_guard, format=format
+ )
+ if sys.version_info < (3, 12, 5) and type_params:
+ # Make use of type_params
+ locals = dict(locals) if locals else {}
+ for tvar in type_params:
+ if tvar.__name__ not in locals: # lets not overwrite something present
+ locals[tvar.__name__] = tvar
+ if sys.version_info < (3, 9):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ )
+ if sys.version_info < (3, 12, 5):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+ if sys.version_info < (3, 14):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ type_params,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ type_params,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ format=format,
+ owner=owner,
+ )
+
+
# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
diff --git a/contrib/python/typing-extensions/py3/ya.make b/contrib/python/typing-extensions/py3/ya.make
index 69f363f940f..bcd18d9ad38 100644
--- a/contrib/python/typing-extensions/py3/ya.make
+++ b/contrib/python/typing-extensions/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(4.12.2)
+VERSION(4.13.2)
LICENSE(PSF-2.0)
diff --git a/contrib/python/ydb/py3/.dist-info/METADATA b/contrib/python/ydb/py3/.dist-info/METADATA
index aa485a3f4c0..3299a5e023b 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.1
+Version: 3.21.2
Summary: YDB Python SDK
Home-page: http://github.com/ydb-platform/ydb-python-sdk
Author: Yandex LLC
@@ -65,3 +65,7 @@ Install YDB python sdk:
```sh
$ python -m pip install ydb
```
+
+## Development
+
+Instructions on `ydb-python-sdk` development are located in [BUILD.md](BUILD.md).
diff --git a/contrib/python/ydb/py3/README.md b/contrib/python/ydb/py3/README.md
index 4cde7ff5bca..7842bd1d910 100644
--- a/contrib/python/ydb/py3/README.md
+++ b/contrib/python/ydb/py3/README.md
@@ -42,3 +42,7 @@ Install YDB python sdk:
```sh
$ python -m pip install ydb
```
+
+## Development
+
+Instructions on `ydb-python-sdk` development are located in [BUILD.md](BUILD.md).
diff --git a/contrib/python/ydb/py3/ya.make b/contrib/python/ydb/py3/ya.make
index 5f37e78726e..bfb67528cff 100644
--- a/contrib/python/ydb/py3/ya.make
+++ b/contrib/python/ydb/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(3.21.1)
+VERSION(3.21.2)
LICENSE(Apache-2.0)
diff --git a/contrib/python/ydb/py3/ydb/aio/pool.py b/contrib/python/ydb/py3/ydb/aio/pool.py
index c8fbb9047e6..99a3cfdb8da 100644
--- a/contrib/python/ydb/py3/ydb/aio/pool.py
+++ b/contrib/python/ydb/py3/ydb/aio/pool.py
@@ -199,12 +199,27 @@ class ConnectionPool(IConnectionPool):
self._store = ConnectionsCache(driver_config.use_all_nodes)
self._grpc_init = Connection(self._driver_config.endpoint, self._driver_config)
self._stopped = False
- self._discovery = Discovery(self._store, self._driver_config)
- self._discovery_task = asyncio.get_event_loop().create_task(self._discovery.run())
+ if driver_config.disable_discovery:
+ # If discovery is disabled, just add the initial endpoint to the store
+ async def init_connection():
+ ready_connection = Connection(self._driver_config.endpoint, self._driver_config)
+ await ready_connection.connection_ready(
+ ready_timeout=getattr(self._driver_config, "discovery_request_timeout", 10)
+ )
+ self._store.add(ready_connection)
+
+ # Create and schedule the task to initialize the connection
+ self._discovery = None
+ self._discovery_task = asyncio.get_event_loop().create_task(init_connection())
+ else:
+ # Start discovery as usual
+ self._discovery = Discovery(self._store, self._driver_config)
+ self._discovery_task = asyncio.get_event_loop().create_task(self._discovery.run())
async def stop(self, timeout=10):
- self._discovery.stop()
+ if self._discovery:
+ self._discovery.stop()
await self._grpc_init.close()
try:
await asyncio.wait_for(self._discovery_task, timeout=timeout)
@@ -215,7 +230,8 @@ class ConnectionPool(IConnectionPool):
def _on_disconnected(self, connection):
async def __wrapper__():
await connection.close()
- self._discovery.notify_disconnected()
+ if self._discovery:
+ self._discovery.notify_disconnected()
return __wrapper__
@@ -223,7 +239,9 @@ class ConnectionPool(IConnectionPool):
await self._store.get(fast_fail=fail_fast, wait_timeout=timeout)
def discovery_debug_details(self):
- return self._discovery.discovery_debug_details()
+ if self._discovery:
+ return self._discovery.discovery_debug_details()
+ return "Discovery is disabled, using only the initial endpoint"
async def __aenter__(self):
return self
@@ -248,7 +266,8 @@ class ConnectionPool(IConnectionPool):
try:
connection = await self._store.get(preferred_endpoint, fast_fail=fast_fail, wait_timeout=wait_timeout)
except BaseException:
- self._discovery.notify_disconnected()
+ if self._discovery:
+ self._discovery.notify_disconnected()
raise
return await connection(
diff --git a/contrib/python/ydb/py3/ydb/driver.py b/contrib/python/ydb/py3/ydb/driver.py
index 3998aeeef5f..09d531d0ddf 100644
--- a/contrib/python/ydb/py3/ydb/driver.py
+++ b/contrib/python/ydb/py3/ydb/driver.py
@@ -96,6 +96,7 @@ class DriverConfig(object):
"grpc_lb_policy_name",
"discovery_request_timeout",
"compression",
+ "disable_discovery",
)
def __init__(
@@ -120,6 +121,7 @@ class DriverConfig(object):
grpc_lb_policy_name="round_robin",
discovery_request_timeout=10,
compression=None,
+ disable_discovery=False,
):
"""
A driver config to initialize a driver instance
@@ -140,6 +142,7 @@ class DriverConfig(object):
If tracing aio ScopeManager must be ContextVarsScopeManager
:param grpc_lb_policy_name: A load balancing policy to be used for discovery channel construction. Default value is `round_round`
:param discovery_request_timeout: A default timeout to complete the discovery. The default value is 10 seconds.
+ :param disable_discovery: If True, endpoint discovery is disabled and only the start endpoint is used for all requests.
"""
self.endpoint = endpoint
@@ -167,6 +170,7 @@ class DriverConfig(object):
self.grpc_lb_policy_name = grpc_lb_policy_name
self.discovery_request_timeout = discovery_request_timeout
self.compression = compression
+ self.disable_discovery = disable_discovery
def set_database(self, database):
self.database = database
diff --git a/contrib/python/ydb/py3/ydb/pool.py b/contrib/python/ydb/py3/ydb/pool.py
index 1e75950ea84..476ea674d9f 100644
--- a/contrib/python/ydb/py3/ydb/pool.py
+++ b/contrib/python/ydb/py3/ydb/pool.py
@@ -350,8 +350,21 @@ class ConnectionPool(IConnectionPool):
self._store = ConnectionsCache(driver_config.use_all_nodes, driver_config.tracer)
self.tracer = driver_config.tracer
self._grpc_init = connection_impl.Connection(self._driver_config.endpoint, self._driver_config)
- self._discovery_thread = Discovery(self._store, self._driver_config)
- self._discovery_thread.start()
+
+ if driver_config.disable_discovery:
+ # If discovery is disabled, just add the initial endpoint to the store
+ ready_connection = connection_impl.Connection.ready_factory(
+ self._driver_config.endpoint,
+ self._driver_config,
+ ready_timeout=getattr(self._driver_config, "discovery_request_timeout", 10),
+ )
+ self._store.add(ready_connection)
+ self._discovery_thread = None
+ else:
+ # Start discovery thread as usual
+ self._discovery_thread = Discovery(self._store, self._driver_config)
+ self._discovery_thread.start()
+
self._stopped = False
self._stop_guard = threading.Lock()
@@ -367,9 +380,11 @@ class ConnectionPool(IConnectionPool):
return
self._stopped = True
- self._discovery_thread.stop()
+ if self._discovery_thread:
+ self._discovery_thread.stop()
self._grpc_init.close()
- self._discovery_thread.join(timeout)
+ if self._discovery_thread:
+ self._discovery_thread.join(timeout)
def async_wait(self, fail_fast=False):
"""
@@ -404,7 +419,13 @@ class ConnectionPool(IConnectionPool):
self._discovery_thread.notify_disconnected()
def discovery_debug_details(self):
- return self._discovery_thread.discovery_debug_details()
+ """
+ Returns debug string about last errors
+ :return: str
+ """
+ if self._discovery_thread:
+ return self._discovery_thread.discovery_debug_details()
+ return "Discovery is disabled, using only the initial endpoint"
@tracing.with_trace()
def __call__(
diff --git a/contrib/python/ydb/py3/ydb/ydb_version.py b/contrib/python/ydb/py3/ydb/ydb_version.py
index 3c62627bc85..62af5284cef 100644
--- a/contrib/python/ydb/py3/ydb/ydb_version.py
+++ b/contrib/python/ydb/py3/ydb/ydb_version.py
@@ -1 +1 @@
-VERSION = "3.21.1"
+VERSION = "3.21.2"