summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorarmenqa <[email protected]>2024-01-19 12:23:50 +0300
committerarmenqa <[email protected]>2024-01-19 13:10:03 +0300
commit2de0149d0151c514b22bca0760b95b26c9b0b578 (patch)
tree2bfed9f3bce7e643ddf048bb61ce3dc0a714bcc2 /contrib/python
parenta8c06d218f12b2406fbce24d194885c5d7b68503 (diff)
feat contrib: aiogram 3
Relates: https://st.yandex-team.ru/, https://st.yandex-team.ru/
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/google-auth/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/google-auth/py3/google/auth/_refresh_worker.py109
-rw-r--r--contrib/python/google-auth/py3/google/auth/credentials.py97
-rw-r--r--contrib/python/google-auth/py3/google/auth/external_account.py10
-rw-r--r--contrib/python/google-auth/py3/google/auth/external_account_authorized_user.py12
-rw-r--r--contrib/python/google-auth/py3/google/auth/impersonated_credentials.py5
-rw-r--r--contrib/python/google-auth/py3/google/auth/version.py2
-rw-r--r--contrib/python/google-auth/py3/google/oauth2/credentials.py17
-rw-r--r--contrib/python/google-auth/py3/google/oauth2/service_account.py10
-rw-r--r--contrib/python/google-auth/py3/tests/oauth2/test_credentials.py12
-rw-r--r--contrib/python/google-auth/py3/tests/test__refresh_worker.py156
-rw-r--r--contrib/python/google-auth/py3/tests/test_credentials.py118
-rw-r--r--contrib/python/google-auth/py3/tests/test_downscoped.py18
-rw-r--r--contrib/python/google-auth/py3/tests/test_external_account.py10
-rw-r--r--contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py31
-rw-r--r--contrib/python/google-auth/py3/tests/test_impersonated_credentials.py2
-rw-r--r--contrib/python/google-auth/py3/ya.make3
-rw-r--r--contrib/python/pg8000/.dist-info/METADATA40
-rw-r--r--contrib/python/pg8000/README.rst38
-rw-r--r--contrib/python/pg8000/pg8000/converters.py154
-rw-r--r--contrib/python/pg8000/pg8000/core.py16
-rw-r--r--contrib/python/pg8000/ya.make2
-rw-r--r--contrib/python/traitlets/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/traitlets/py3/tests/_warnings.py1
-rw-r--r--contrib/python/traitlets/py3/tests/config/test_application.py10
-rw-r--r--contrib/python/traitlets/py3/tests/config/test_argcomplete.py8
-rw-r--r--contrib/python/traitlets/py3/tests/config/test_configurable.py5
-rw-r--r--contrib/python/traitlets/py3/tests/config/test_loader.py6
-rw-r--r--contrib/python/traitlets/py3/tests/test_traitlets.py19
-rw-r--r--contrib/python/traitlets/py3/tests/test_traitlets_docstring.py2
-rw-r--r--contrib/python/traitlets/py3/tests/test_traitlets_enum.py3
-rw-r--r--contrib/python/traitlets/py3/tests/utils/test_bunch.py2
-rw-r--r--contrib/python/traitlets/py3/tests/utils/test_decorators.py2
-rw-r--r--contrib/python/traitlets/py3/tests/utils/test_importstring.py1
-rw-r--r--contrib/python/traitlets/py3/traitlets/__init__.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/_version.py4
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/__init__.py4
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/application.py14
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py4
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/configurable.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/loader.py24
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/manager.py3
-rw-r--r--contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py3
-rw-r--r--contrib/python/traitlets/py3/traitlets/log.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/tests/utils.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/traitlets.py62
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/decorators.py1
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/descriptions.py3
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/getargspec.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/importstring.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/nested_update.py2
-rw-r--r--contrib/python/traitlets/py3/traitlets/utils/text.py4
-rw-r--r--contrib/python/traitlets/py3/ya.make2
53 files changed, 849 insertions, 218 deletions
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA
index f86d77d41bb..21345a05554 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.25.2
+Version: 2.26.1
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/_refresh_worker.py b/contrib/python/google-auth/py3/google/auth/_refresh_worker.py
new file mode 100644
index 00000000000..9bb0ccc2c59
--- /dev/null
+++ b/contrib/python/google-auth/py3/google/auth/_refresh_worker.py
@@ -0,0 +1,109 @@
+# Copyright 2023 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 copy
+import logging
+import threading
+
+import google.auth.exceptions as e
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class RefreshThreadManager:
+ """
+ Organizes exactly one background job that refresh a token.
+ """
+
+ def __init__(self):
+ """Initializes the manager."""
+
+ self._worker = None
+ self._lock = threading.Lock() # protects access to worker threads.
+
+ def start_refresh(self, cred, request):
+ """Starts a refresh thread for the given credentials.
+ The credentials are refreshed using the request parameter.
+ request and cred MUST not be None
+
+ Returns True if a background refresh was kicked off. False otherwise.
+
+ Args:
+ cred: A credentials object.
+ request: A request object.
+ Returns:
+ bool
+ """
+ if cred is None or request is None:
+ raise e.InvalidValue(
+ "Unable to start refresh. cred and request must be valid and instantiated objects."
+ )
+
+ with self._lock:
+ if self._worker is not None and self._worker._error_info is not None:
+ return False
+
+ if self._worker is None or not self._worker.is_alive(): # pragma: NO COVER
+ self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request))
+ self._worker.start()
+ return True
+
+ def clear_error(self):
+ """
+ Removes any errors that were stored from previous background refreshes.
+ """
+ with self._lock:
+ if self._worker:
+ self._worker._error_info = None
+
+ def __getstate__(self):
+ """Pickle helper that serializes the _lock attribute."""
+ state = self.__dict__.copy()
+ state["_lock"] = None
+ return state
+
+ def __setstate__(self, state):
+ """Pickle helper that deserializes the _lock attribute."""
+ state["_key"] = threading.Lock()
+ self.__dict__.update(state)
+
+
+class RefreshThread(threading.Thread):
+ """
+ Thread that refreshes credentials.
+ """
+
+ def __init__(self, cred, request, **kwargs):
+ """Initializes the thread.
+
+ Args:
+ cred: A Credential object to refresh.
+ request: A Request object used to perform a credential refresh.
+ **kwargs: Additional keyword arguments.
+ """
+
+ super().__init__(**kwargs)
+ self._cred = cred
+ self._request = request
+ self._error_info = None
+
+ def run(self):
+ """
+ Perform the credential refresh.
+ """
+ try:
+ self._cred.refresh(self._request)
+ except Exception as err: # pragma: NO COVER
+ _LOGGER.error(f"Background refresh failed due to: {err}")
+ self._error_info = err
diff --git a/contrib/python/google-auth/py3/google/auth/credentials.py b/contrib/python/google-auth/py3/google/auth/credentials.py
index 800781c4089..a4fa1829c72 100644
--- a/contrib/python/google-auth/py3/google/auth/credentials.py
+++ b/contrib/python/google-auth/py3/google/auth/credentials.py
@@ -16,11 +16,13 @@
"""Interfaces for credentials."""
import abc
+from enum import Enum
import os
from google.auth import _helpers, environment_vars
from google.auth import exceptions
from google.auth import metrics
+from google.auth._refresh_worker import RefreshThreadManager
class Credentials(metaclass=abc.ABCMeta):
@@ -59,6 +61,9 @@ class Credentials(metaclass=abc.ABCMeta):
"""Optional[str]: The universe domain value, default is googleapis.com
"""
+ self._use_non_blocking_refresh = False
+ self._refresh_worker = RefreshThreadManager()
+
@property
def expired(self):
"""Checks if the credentials are expired.
@@ -66,10 +71,12 @@ class Credentials(metaclass=abc.ABCMeta):
Note that credentials can be invalid but not expired because
Credentials with :attr:`expiry` set to None is considered to never
expire.
+
+ .. deprecated:: v2.24.0
+ Prefer checking :attr:`token_state` instead.
"""
if not self.expiry:
return False
-
# Remove some threshold from expiry to err on the side of reporting
# expiration early so that we avoid the 401-refresh-retry loop.
skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
@@ -81,10 +88,35 @@ class Credentials(metaclass=abc.ABCMeta):
This is True if the credentials have a :attr:`token` and the token
is not :attr:`expired`.
+
+ .. deprecated:: v2.24.0
+ Prefer checking :attr:`token_state` instead.
"""
return self.token is not None and not self.expired
@property
+ def token_state(self):
+ """
+ See `:obj:`TokenState`
+ """
+ if self.token is None:
+ return TokenState.INVALID
+
+ # Credentials that can't expire are always treated as fresh.
+ if self.expiry is None:
+ return TokenState.FRESH
+
+ expired = _helpers.utcnow() >= self.expiry
+ if expired:
+ return TokenState.INVALID
+
+ is_stale = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD)
+ if is_stale:
+ return TokenState.STALE
+
+ return TokenState.FRESH
+
+ @property
def quota_project_id(self):
"""Project to use for quota and billing purposes."""
return self._quota_project_id
@@ -154,6 +186,25 @@ class Credentials(metaclass=abc.ABCMeta):
if self.quota_project_id:
headers["x-goog-user-project"] = self.quota_project_id
+ def _blocking_refresh(self, request):
+ if not self.valid:
+ self.refresh(request)
+
+ def _non_blocking_refresh(self, request):
+ use_blocking_refresh_fallback = False
+
+ if self.token_state == TokenState.STALE:
+ use_blocking_refresh_fallback = not self._refresh_worker.start_refresh(
+ self, request
+ )
+
+ if self.token_state == TokenState.INVALID or use_blocking_refresh_fallback:
+ self.refresh(request)
+ # If the blocking refresh succeeds then we can clear the error info
+ # on the background refresh worker, and perform refreshes in a
+ # background thread.
+ self._refresh_worker.clear_error()
+
def before_request(self, request, method, url, headers):
"""Performs credential-specific before request logic.
@@ -171,11 +222,17 @@ class Credentials(metaclass=abc.ABCMeta):
# pylint: disable=unused-argument
# (Subclasses may use these arguments to ascertain information about
# the http request.)
- if not self.valid:
- self.refresh(request)
+ if self._use_non_blocking_refresh:
+ self._non_blocking_refresh(request)
+ else:
+ self._blocking_refresh(request)
+
metrics.add_metric_header(headers, self._metric_header_for_usage())
self.apply(headers)
+ def with_non_blocking_refresh(self):
+ self._use_non_blocking_refresh = True
+
class CredentialsWithQuotaProject(Credentials):
"""Abstract base for credentials supporting ``with_quota_project`` factory"""
@@ -188,7 +245,7 @@ class CredentialsWithQuotaProject(Credentials):
billing purposes
Returns:
- google.oauth2.credentials.Credentials: A new credentials instance.
+ google.auth.credentials.Credentials: A new credentials instance.
"""
raise NotImplementedError("This credential does not support quota project.")
@@ -209,11 +266,28 @@ class CredentialsWithTokenUri(Credentials):
token_uri (str): The uri to use for fetching/exchanging tokens
Returns:
- google.oauth2.credentials.Credentials: A new credentials instance.
+ google.auth.credentials.Credentials: A new credentials instance.
"""
raise NotImplementedError("This credential does not use token uri.")
+class CredentialsWithUniverseDomain(Credentials):
+ """Abstract base for credentials supporting ``with_universe_domain`` factory"""
+
+ def with_universe_domain(self, universe_domain):
+ """Returns a copy of these credentials with a modified universe domain.
+
+ Args:
+ universe_domain (str): The universe domain to use
+
+ Returns:
+ google.auth.credentials.Credentials: A new credentials instance.
+ """
+ raise NotImplementedError(
+ "This credential does not support with_universe_domain."
+ )
+
+
class AnonymousCredentials(Credentials):
"""Credentials that do not provide any authentication information.
@@ -422,3 +496,16 @@ class Signing(metaclass=abc.ABCMeta):
# pylint: disable=missing-raises-doc
# (pylint doesn't recognize that this is abstract)
raise NotImplementedError("Signer must be implemented.")
+
+
+class TokenState(Enum):
+ """
+ Tracks the state of a token.
+ FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry.
+ STALE: The token is close to expired, and should be refreshed. The token can be used normally.
+ INVALID: The token is expired or invalid. The token cannot be used for a normal operation.
+ """
+
+ FRESH = 1
+ STALE = 2
+ INVALID = 3
diff --git a/contrib/python/google-auth/py3/google/auth/external_account.py b/contrib/python/google-auth/py3/google/auth/external_account.py
index e7fed8695ad..c314ea799ea 100644
--- a/contrib/python/google-auth/py3/google/auth/external_account.py
+++ b/contrib/python/google-auth/py3/google/auth/external_account.py
@@ -415,16 +415,8 @@ class Credentials(
new_cred._metrics_options = self._metrics_options
return new_cred
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
def with_universe_domain(self, universe_domain):
- """Create a copy of these credentials with the given universe domain.
-
- Args:
- universe_domain (str): The universe domain value.
-
- Returns:
- google.auth.external_account.Credentials: A new credentials
- instance.
- """
kwargs = self._constructor_args()
kwargs.update(universe_domain=universe_domain)
new_cred = self.__class__(**kwargs)
diff --git a/contrib/python/google-auth/py3/google/auth/external_account_authorized_user.py b/contrib/python/google-auth/py3/google/auth/external_account_authorized_user.py
index a2d4edf6ffd..55230103f43 100644
--- a/contrib/python/google-auth/py3/google/auth/external_account_authorized_user.py
+++ b/contrib/python/google-auth/py3/google/auth/external_account_authorized_user.py
@@ -43,6 +43,7 @@ from google.auth import exceptions
from google.oauth2 import sts
from google.oauth2 import utils
+_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user"
@@ -75,6 +76,7 @@ class Credentials(
revoke_url=None,
scopes=None,
quota_project_id=None,
+ universe_domain=_DEFAULT_UNIVERSE_DOMAIN,
):
"""Instantiates a external account authorized user credentials object.
@@ -98,6 +100,8 @@ class Credentials(
quota_project_id (str): The optional project ID used for quota and billing.
This project may be different from the project used to
create the credentials.
+ universe_domain (Optional[str]): The universe domain. The default value
+ is googleapis.com.
Returns:
google.auth.external_account_authorized_user.Credentials: The
@@ -116,6 +120,7 @@ class Credentials(
self._revoke_url = revoke_url
self._quota_project_id = quota_project_id
self._scopes = scopes
+ self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN
if not self.valid and not self.can_refresh:
raise exceptions.InvalidOperation(
@@ -162,6 +167,7 @@ class Credentials(
"revoke_url": self._revoke_url,
"scopes": self._scopes,
"quota_project_id": self._quota_project_id,
+ "universe_domain": self._universe_domain,
}
@property
@@ -297,6 +303,12 @@ class Credentials(
kwargs.update(token_url=token_uri)
return self.__class__(**kwargs)
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
+ def with_universe_domain(self, universe_domain):
+ kwargs = self.constructor_args()
+ kwargs.update(universe_domain=universe_domain)
+ return self.__class__(**kwargs)
+
@classmethod
def from_info(cls, info, **kwargs):
"""Creates a Credentials instance from parsed external account info.
diff --git a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py
index c272a3ca28b..d32e6eb69a5 100644
--- a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py
+++ b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py
@@ -259,7 +259,10 @@ class Credentials(
"""
# Refresh our source credentials if it is not valid.
- if not self._source_credentials.valid:
+ if (
+ self._source_credentials.token_state == credentials.TokenState.STALE
+ or self._source_credentials.token_state == credentials.TokenState.INVALID
+ ):
self._source_credentials.refresh(request)
body = {
diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py
index 31cc30242a2..1c94c2f5f61 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.25.2"
+__version__ = "2.26.1"
diff --git a/contrib/python/google-auth/py3/google/oauth2/credentials.py b/contrib/python/google-auth/py3/google/oauth2/credentials.py
index a5c93ecc2f8..41f4a05bb6e 100644
--- a/contrib/python/google-auth/py3/google/oauth2/credentials.py
+++ b/contrib/python/google-auth/py3/google/oauth2/credentials.py
@@ -160,7 +160,11 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr
# unpickling certain callables (lambda, functools.partial instances)
# because they need to be importable.
# Instead, the refresh_handler setter should be used to repopulate this.
- del state_dict["_refresh_handler"]
+ if "_refresh_handler" in state_dict:
+ del state_dict["_refresh_handler"]
+
+ if "_refresh_worker" in state_dict:
+ del state_dict["_refresh_worker"]
return state_dict
def __setstate__(self, d):
@@ -183,6 +187,8 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr
self._universe_domain = d.get("_universe_domain") or _DEFAULT_UNIVERSE_DOMAIN
# The refresh_handler setter should be used to repopulate this.
self._refresh_handler = None
+ self._refresh_worker = None
+ self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False)
@property
def refresh_token(self):
@@ -302,15 +308,8 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr
universe_domain=self._universe_domain,
)
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
def with_universe_domain(self, universe_domain):
- """Create a copy of the credential with the given universe domain.
-
- Args:
- universe_domain (str): The universe domain value.
-
- Returns:
- google.oauth2.credentials.Credentials: A new credentials instance.
- """
return self.__class__(
self.token,
diff --git a/contrib/python/google-auth/py3/google/oauth2/service_account.py b/contrib/python/google-auth/py3/google/oauth2/service_account.py
index 68db41af409..4502c6f68c6 100644
--- a/contrib/python/google-auth/py3/google/oauth2/service_account.py
+++ b/contrib/python/google-auth/py3/google/oauth2/service_account.py
@@ -325,16 +325,8 @@ class Credentials(
cred._always_use_jwt_access = always_use_jwt_access
return cred
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
def with_universe_domain(self, universe_domain):
- """Create a copy of these credentials with the given universe domain.
-
- Args:
- universe_domain (str): The universe domain value.
-
- Returns:
- google.auth.service_account.Credentials: A new credentials
- instance.
- """
cred = self._make_copy()
cred._universe_domain = universe_domain
if universe_domain != _DEFAULT_UNIVERSE_DOMAIN:
diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py b/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
index d6a19158627..5f1dcf3cbfc 100644
--- a/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
+++ b/contrib/python/google-auth/py3/tests/oauth2/test_credentials.py
@@ -24,6 +24,7 @@ import pytest # type: ignore
from google.auth import _helpers
from google.auth import exceptions
from google.auth import transport
+from google.auth.credentials import TokenState
from google.oauth2 import credentials
@@ -62,6 +63,7 @@ class TestCredentials(object):
assert not credentials.expired
# Scopes aren't required for these credentials
assert not credentials.requires_scopes
+ assert credentials.token_state == TokenState.INVALID
# Test properties
assert credentials.refresh_token == self.REFRESH_TOKEN
assert credentials.token_uri == self.TOKEN_URI
@@ -912,7 +914,11 @@ class TestCredentials(object):
assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
for attr in list(creds.__dict__):
- assert getattr(creds, attr) == getattr(unpickled, attr)
+ # Worker should always be None
+ if attr == "_refresh_worker":
+ assert getattr(unpickled, attr) is None
+ else:
+ assert getattr(creds, attr) == getattr(unpickled, attr)
def test_pickle_and_unpickle_universe_domain(self):
# old version of auth lib doesn't have _universe_domain, so the pickled
@@ -946,7 +952,7 @@ class TestCredentials(object):
for attr in list(creds.__dict__):
# For the _refresh_handler property, the unpickled creds should be
# set to None.
- if attr == "_refresh_handler":
+ if attr == "_refresh_handler" or attr == "_refresh_worker":
assert getattr(unpickled, attr) is None
else:
assert getattr(creds, attr) == getattr(unpickled, attr)
@@ -958,6 +964,8 @@ class TestCredentials(object):
# this mimics a pickle created with a previous class definition with
# fewer attributes
del creds.__dict__["_quota_project_id"]
+ del creds.__dict__["_refresh_handler"]
+ del creds.__dict__["_refresh_worker"]
unpickled = pickle.loads(pickle.dumps(creds))
diff --git a/contrib/python/google-auth/py3/tests/test__refresh_worker.py b/contrib/python/google-auth/py3/tests/test__refresh_worker.py
new file mode 100644
index 00000000000..f842b02cac4
--- /dev/null
+++ b/contrib/python/google-auth/py3/tests/test__refresh_worker.py
@@ -0,0 +1,156 @@
+# Copyright 2023 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 pickle
+import random
+import threading
+import time
+
+import mock
+import pytest # type: ignore
+
+from google.auth import _refresh_worker, credentials, exceptions
+
+MAIN_THREAD_SLEEP_MS = 100 / 1000
+
+
+class MockCredentialsImpl(credentials.Credentials):
+ def __init__(self, sleep_seconds=None):
+ self.refresh_count = 0
+ self.token = None
+ self.sleep_seconds = sleep_seconds if sleep_seconds else None
+
+ def refresh(self, request):
+ if self.sleep_seconds:
+ time.sleep(self.sleep_seconds)
+ self.token = request
+ self.refresh_count += 1
+
+
+def test_thread_count():
+ return 25
+
+
+def _cred_spinlock(cred):
+ while cred.token is None: # pragma: NO COVER
+ time.sleep(MAIN_THREAD_SLEEP_MS)
+
+
+def test_invalid_start_refresh():
+ w = _refresh_worker.RefreshThreadManager()
+ with pytest.raises(exceptions.InvalidValue):
+ w.start_refresh(None, None)
+
+
+def test_start_refresh():
+ w = _refresh_worker.RefreshThreadManager()
+ cred = MockCredentialsImpl()
+ request = mock.MagicMock()
+ assert w.start_refresh(cred, request)
+
+ assert w._worker is not None
+
+ _cred_spinlock(cred)
+
+ assert cred.token == request
+ assert cred.refresh_count == 1
+
+
+def test_nonblocking_start_refresh():
+ w = _refresh_worker.RefreshThreadManager()
+ cred = MockCredentialsImpl(sleep_seconds=1)
+ request = mock.MagicMock()
+ assert w.start_refresh(cred, request)
+
+ assert w._worker is not None
+ assert not cred.token
+ assert cred.refresh_count == 0
+
+
+def test_multiple_refreshes_multiple_workers(test_thread_count):
+ w = _refresh_worker.RefreshThreadManager()
+ cred = MockCredentialsImpl()
+ request = mock.MagicMock()
+
+ def _thread_refresh():
+ time.sleep(random.randrange(0, 5))
+ assert w.start_refresh(cred, request)
+
+ threads = [
+ threading.Thread(target=_thread_refresh) for _ in range(test_thread_count)
+ ]
+ for t in threads:
+ t.start()
+
+ _cred_spinlock(cred)
+
+ assert cred.token == request
+ # There is a chance only one thread has enough time to perform a refresh.
+ # Generally multiple threads will have time to perform a refresh
+ assert cred.refresh_count > 0
+
+
+def test_refresh_error():
+ w = _refresh_worker.RefreshThreadManager()
+ cred = mock.MagicMock()
+ request = mock.MagicMock()
+
+ cred.refresh.side_effect = exceptions.RefreshError("Failed to refresh")
+
+ assert w.start_refresh(cred, request)
+
+ while w._worker._error_info is None: # pragma: NO COVER
+ time.sleep(MAIN_THREAD_SLEEP_MS)
+
+ assert w._worker is not None
+ assert isinstance(w._worker._error_info, exceptions.RefreshError)
+
+
+def test_refresh_error_call_refresh_again():
+ w = _refresh_worker.RefreshThreadManager()
+ cred = mock.MagicMock()
+ request = mock.MagicMock()
+
+ cred.refresh.side_effect = exceptions.RefreshError("Failed to refresh")
+
+ assert w.start_refresh(cred, request)
+
+ while w._worker._error_info is None: # pragma: NO COVER
+ time.sleep(MAIN_THREAD_SLEEP_MS)
+
+ assert not w.start_refresh(cred, request)
+
+
+def test_refresh_dead_worker():
+ cred = MockCredentialsImpl()
+ request = mock.MagicMock()
+
+ w = _refresh_worker.RefreshThreadManager()
+ w._worker = None
+
+ w.start_refresh(cred, request)
+
+ _cred_spinlock(cred)
+
+ assert cred.token == request
+ assert cred.refresh_count == 1
+
+
+def test_pickle():
+ w = _refresh_worker.RefreshThreadManager()
+
+ pickled_manager = pickle.dumps(w)
+ manager = pickle.loads(pickled_manager)
+ assert isinstance(manager, _refresh_worker.RefreshThreadManager)
diff --git a/contrib/python/google-auth/py3/tests/test_credentials.py b/contrib/python/google-auth/py3/tests/test_credentials.py
index d64f3abb506..8e6bbc96330 100644
--- a/contrib/python/google-auth/py3/tests/test_credentials.py
+++ b/contrib/python/google-auth/py3/tests/test_credentials.py
@@ -14,6 +14,7 @@
import datetime
+import mock
import pytest # type: ignore
from google.auth import _helpers
@@ -23,6 +24,11 @@ from google.auth import credentials
class CredentialsImpl(credentials.Credentials):
def refresh(self, request):
self.token = request
+ self.expiry = (
+ datetime.datetime.utcnow()
+ + _helpers.REFRESH_THRESHOLD
+ + datetime.timedelta(seconds=5)
+ )
def with_quota_project(self, quota_project_id):
raise NotImplementedError()
@@ -43,6 +49,13 @@ def test_credentials_constructor():
assert not credentials.expired
assert not credentials.valid
assert credentials.universe_domain == "googleapis.com"
+ assert not credentials._use_non_blocking_refresh
+
+
+def test_with_non_blocking_refresh():
+ c = CredentialsImpl()
+ c.with_non_blocking_refresh()
+ assert c._use_non_blocking_refresh
def test_expired_and_valid():
@@ -220,3 +233,108 @@ def test_create_scoped_if_required_not_scopes():
)
assert scoped_credentials is unscoped_credentials
+
+
+def test_nonblocking_refresh_fresh_credentials():
+ c = CredentialsImpl()
+
+ c._refresh_worker = mock.MagicMock()
+
+ request = "token"
+
+ c.refresh(request)
+ assert c.token_state == credentials.TokenState.FRESH
+
+ c.with_non_blocking_refresh()
+ c.before_request(request, "http://example.com", "GET", {})
+
+
+def test_nonblocking_refresh_invalid_credentials():
+ c = CredentialsImpl()
+ c.with_non_blocking_refresh()
+
+ request = "token"
+ headers = {}
+
+ assert c.token_state == credentials.TokenState.INVALID
+
+ c.before_request(request, "http://example.com", "GET", headers)
+ assert c.token_state == credentials.TokenState.FRESH
+ assert c.valid
+ assert c.token == "token"
+ assert headers["authorization"] == "Bearer token"
+ assert "x-identity-trust-boundary" not in headers
+
+
+def test_nonblocking_refresh_stale_credentials():
+ c = CredentialsImpl()
+ c.with_non_blocking_refresh()
+
+ request = "token"
+ headers = {}
+
+ # Invalid credentials MUST require a blocking refresh.
+ c.before_request(request, "http://example.com", "GET", headers)
+ assert c.token_state == credentials.TokenState.FRESH
+ assert not c._refresh_worker._worker
+
+ c.expiry = (
+ datetime.datetime.utcnow()
+ + _helpers.REFRESH_THRESHOLD
+ - datetime.timedelta(seconds=1)
+ )
+
+ # STALE credentials SHOULD spawn a non-blocking worker
+ assert c.token_state == credentials.TokenState.STALE
+ c.before_request(request, "http://example.com", "GET", headers)
+ assert c._refresh_worker._worker is not None
+
+ assert c.token_state == credentials.TokenState.FRESH
+ assert c.valid
+ assert c.token == "token"
+ assert headers["authorization"] == "Bearer token"
+ assert "x-identity-trust-boundary" not in headers
+
+
+def test_nonblocking_refresh_failed_credentials():
+ c = CredentialsImpl()
+ c.with_non_blocking_refresh()
+
+ request = "token"
+ headers = {}
+
+ # Invalid credentials MUST require a blocking refresh.
+ c.before_request(request, "http://example.com", "GET", headers)
+ assert c.token_state == credentials.TokenState.FRESH
+ assert not c._refresh_worker._worker
+
+ c.expiry = (
+ datetime.datetime.utcnow()
+ + _helpers.REFRESH_THRESHOLD
+ - datetime.timedelta(seconds=1)
+ )
+
+ # STALE credentials SHOULD spawn a non-blocking worker
+ assert c.token_state == credentials.TokenState.STALE
+ c._refresh_worker._worker = mock.MagicMock()
+ c._refresh_worker._worker._error_info = "Some Error"
+ c.before_request(request, "http://example.com", "GET", headers)
+ assert c._refresh_worker._worker is not None
+
+ assert c.token_state == credentials.TokenState.FRESH
+ assert c.valid
+ assert c.token == "token"
+ assert headers["authorization"] == "Bearer token"
+ assert "x-identity-trust-boundary" not in headers
+
+
+def test_token_state_no_expiry():
+ c = CredentialsImpl()
+
+ request = "token"
+ c.refresh(request)
+
+ c.expiry = None
+ assert c.token_state == credentials.TokenState.FRESH
+
+ c.before_request(request, "http://example.com", "GET", {})
diff --git a/contrib/python/google-auth/py3/tests/test_downscoped.py b/contrib/python/google-auth/py3/tests/test_downscoped.py
index b011380bdbb..8cc2a30d163 100644
--- a/contrib/python/google-auth/py3/tests/test_downscoped.py
+++ b/contrib/python/google-auth/py3/tests/test_downscoped.py
@@ -25,6 +25,7 @@ from google.auth import credentials
from google.auth import downscoped
from google.auth import exceptions
from google.auth import transport
+from google.auth.credentials import TokenState
EXPRESSION = (
@@ -676,6 +677,7 @@ class TestCredentials(object):
assert credentials.valid
assert not credentials.expired
+ assert credentials.token_state == TokenState.FRESH
credentials.before_request(request, "POST", "https://example.com/api", headers)
@@ -687,8 +689,24 @@ class TestCredentials(object):
assert not credentials.valid
assert credentials.expired
+ assert credentials.token_state == TokenState.STALE
credentials.before_request(request, "POST", "https://example.com/api", headers)
+ assert credentials.token_state == TokenState.FRESH
+
+ # New token should be retrieved.
+ assert headers == {
+ "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"])
+ }
+
+ utcnow.return_value = datetime.datetime.min + datetime.timedelta(seconds=6000)
+
+ assert not credentials.valid
+ assert credentials.expired
+ assert credentials.token_state == TokenState.INVALID
+
+ credentials.before_request(request, "POST", "https://example.com/api", headers)
+ assert credentials.token_state == TokenState.FRESH
# New token should be retrieved.
assert headers == {
diff --git a/contrib/python/google-auth/py3/tests/test_external_account.py b/contrib/python/google-auth/py3/tests/test_external_account.py
index 5225dcf3424..7f33b1dfa2a 100644
--- a/contrib/python/google-auth/py3/tests/test_external_account.py
+++ b/contrib/python/google-auth/py3/tests/test_external_account.py
@@ -24,6 +24,7 @@ from google.auth import _helpers
from google.auth import exceptions
from google.auth import external_account
from google.auth import transport
+from google.auth.credentials import TokenState
IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = (
@@ -1494,6 +1495,7 @@ class TestCredentials(object):
assert credentials.valid
assert not credentials.expired
+ assert credentials.token_state == TokenState.FRESH
credentials.before_request(request, "POST", "https://example.com/api", headers)
@@ -1508,8 +1510,10 @@ class TestCredentials(object):
assert not credentials.valid
assert credentials.expired
+ assert credentials.token_state == TokenState.STALE
credentials.before_request(request, "POST", "https://example.com/api", headers)
+ assert credentials.token_state == TokenState.FRESH
# New token should be retrieved.
assert headers == {
@@ -1551,8 +1555,10 @@ class TestCredentials(object):
assert credentials.valid
assert not credentials.expired
+ assert credentials.token_state == TokenState.FRESH
credentials.before_request(request, "POST", "https://example.com/api", headers)
+ assert credentials.token_state == TokenState.FRESH
# Cached token should be used.
assert headers == {
@@ -1566,6 +1572,10 @@ class TestCredentials(object):
assert not credentials.valid
assert credentials.expired
+ assert credentials.token_state == TokenState.STALE
+
+ credentials.before_request(request, "POST", "https://example.com/api", headers)
+ assert credentials.token_state == TokenState.FRESH
credentials.before_request(request, "POST", "https://example.com/api", headers)
diff --git a/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py b/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
index 7ffd5078c81..7213a23486c 100644
--- a/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
+++ b/contrib/python/google-auth/py3/tests/test_external_account_authorized_user.py
@@ -44,6 +44,8 @@ CLIENT_SECRET = "password"
BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
SCOPES = ["email", "profile"]
NOW = datetime.datetime(1990, 8, 27, 6, 54, 30)
+FAKE_UNIVERSE_DOMAIN = "fake-universe-domain"
+DEFAULT_UNIVERSE_DOMAIN = external_account_authorized_user._DEFAULT_UNIVERSE_DOMAIN
class TestCredentials(object):
@@ -98,6 +100,7 @@ class TestCredentials(object):
assert creds.refresh_token == REFRESH_TOKEN
assert creds.audience == AUDIENCE
assert creds.token_url == TOKEN_URL
+ assert creds.universe_domain == DEFAULT_UNIVERSE_DOMAIN
def test_basic_create(self):
creds = external_account_authorized_user.Credentials(
@@ -105,6 +108,7 @@ class TestCredentials(object):
expiry=datetime.datetime.max,
scopes=SCOPES,
revoke_url=REVOKE_URL,
+ universe_domain=FAKE_UNIVERSE_DOMAIN,
)
assert creds.expiry == datetime.datetime.max
@@ -115,6 +119,7 @@ class TestCredentials(object):
assert creds.scopes == SCOPES
assert creds.is_user
assert creds.revoke_url == REVOKE_URL
+ assert creds.universe_domain == FAKE_UNIVERSE_DOMAIN
def test_stunted_create_no_refresh_token(self):
with pytest.raises(ValueError) as excinfo:
@@ -339,6 +344,7 @@ class TestCredentials(object):
assert info["token_info_url"] == TOKEN_INFO_URL
assert info["client_id"] == CLIENT_ID
assert info["client_secret"] == CLIENT_SECRET
+ assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN
assert "token" not in info
assert "expiry" not in info
assert "revoke_url" not in info
@@ -350,6 +356,7 @@ class TestCredentials(object):
expiry=NOW,
revoke_url=REVOKE_URL,
quota_project_id=QUOTA_PROJECT_ID,
+ universe_domain=FAKE_UNIVERSE_DOMAIN,
)
info = creds.info
@@ -363,6 +370,7 @@ class TestCredentials(object):
assert info["expiry"] == NOW.isoformat() + "Z"
assert info["revoke_url"] == REVOKE_URL
assert info["quota_project_id"] == QUOTA_PROJECT_ID
+ assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN
def test_to_json(self):
creds = self.make_credentials()
@@ -375,6 +383,7 @@ class TestCredentials(object):
assert info["token_info_url"] == TOKEN_INFO_URL
assert info["client_id"] == CLIENT_ID
assert info["client_secret"] == CLIENT_SECRET
+ assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN
assert "token" not in info
assert "expiry" not in info
assert "revoke_url" not in info
@@ -386,6 +395,7 @@ class TestCredentials(object):
expiry=NOW,
revoke_url=REVOKE_URL,
quota_project_id=QUOTA_PROJECT_ID,
+ universe_domain=FAKE_UNIVERSE_DOMAIN,
)
json_info = creds.to_json()
info = json.loads(json_info)
@@ -400,6 +410,7 @@ class TestCredentials(object):
assert info["expiry"] == NOW.isoformat() + "Z"
assert info["revoke_url"] == REVOKE_URL
assert info["quota_project_id"] == QUOTA_PROJECT_ID
+ assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN
def test_to_json_full_with_strip(self):
creds = self.make_credentials(
@@ -467,6 +478,26 @@ class TestCredentials(object):
assert new_creds._revoke_url == creds._revoke_url
assert new_creds._quota_project_id == creds._quota_project_id
+ def test_with_universe_domain(self):
+ creds = self.make_credentials(
+ token=ACCESS_TOKEN,
+ expiry=NOW,
+ revoke_url=REVOKE_URL,
+ quota_project_id=QUOTA_PROJECT_ID,
+ )
+ new_creds = creds.with_universe_domain(FAKE_UNIVERSE_DOMAIN)
+ assert new_creds._audience == creds._audience
+ assert new_creds._refresh_token == creds._refresh_token
+ assert new_creds._token_url == creds._token_url
+ assert new_creds._token_info_url == creds._token_info_url
+ assert new_creds._client_id == creds._client_id
+ assert new_creds._client_secret == creds._client_secret
+ assert new_creds.token == creds.token
+ assert new_creds.expiry == creds.expiry
+ assert new_creds._revoke_url == creds._revoke_url
+ assert new_creds._quota_project_id == QUOTA_PROJECT_ID
+ assert new_creds.universe_domain == FAKE_UNIVERSE_DOMAIN
+
def test_from_file_required_options_only(self, tmpdir):
from_creds = self.make_credentials()
config_file = tmpdir.join("config.json")
diff --git a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
index 9696e823fff..7295bba4292 100644
--- a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
+++ b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py
@@ -243,7 +243,7 @@ class TestImpersonatedCredentials(object):
request_kwargs = request.call_args[1]
assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE
- @pytest.mark.parametrize("time_skew", [100, -100])
+ @pytest.mark.parametrize("time_skew", [150, -150])
def test_refresh_source_credentials(self, time_skew):
credentials = self.make_credentials(lifetime=None)
diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make
index ec71907cc6b..75848da9710 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.25.2)
+VERSION(2.26.1)
LICENSE(Apache-2.0)
@@ -34,6 +34,7 @@ PY_SRCS(
google/auth/_helpers.py
google/auth/_jwt_async.py
google/auth/_oauth2client.py
+ google/auth/_refresh_worker.py
google/auth/_service_account_info.py
google/auth/api_key.py
google/auth/app_engine.py
diff --git a/contrib/python/pg8000/.dist-info/METADATA b/contrib/python/pg8000/.dist-info/METADATA
index 3511e11a2a8..93aec222db4 100644
--- a/contrib/python/pg8000/.dist-info/METADATA
+++ b/contrib/python/pg8000/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pg8000
-Version: 1.30.3
+Version: 1.30.4
Summary: PostgreSQL interface library
License: BSD 3-Clause License
Project-URL: Homepage, https://github.com/tlocke/pg8000
@@ -39,7 +39,7 @@ pg8000
pg8000 is a pure-`Python <https://www.python.org/>`_
`PostgreSQL <http://www.postgresql.org/>`_ driver that complies with
`DB-API 2.0 <http://www.python.org/dev/peps/pep-0249/>`_. It is tested on Python
-versions 3.8+, on CPython and PyPy, and PostgreSQL versions 11+. pg8000's name comes
+versions 3.8+, on CPython and PyPy, and PostgreSQL versions 12+. pg8000's name comes
from the belief that it is probably about the 8000th PostgreSQL interface for Python.
pg8000 is distributed under the BSD 3-clause license.
@@ -303,9 +303,7 @@ PostgreSQL `notices
<https://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html>`_ are
stored in a deque called ``Connection.notices`` and added using the ``append()``
method. Similarly there are ``Connection.notifications`` for `notifications
-<https://www.postgresql.org/docs/current/static/sql-notify.html>`_ and
-``Connection.parameter_statuses`` for changes to the server configuration. Here's an
-example:
+<https://www.postgresql.org/docs/current/static/sql-notify.html>`_. Here's an example:
>>> import pg8000.native
>>>
@@ -321,6 +319,26 @@ example:
>>> con.close()
+Parameter Statuses
+``````````````````
+
+`Certain parameter values are reported by the server automatically at connection startup or whenever
+their values change
+<https://www.postgresql.org/docs/current/libpq-status.html#LIBPQ-PQPARAMETERSTATUS>`_ and pg8000
+stores the latest values in a dict called ``Connection.parameter_statuses``. Here's an example where
+we set the ``aplication_name`` parameter and then read it from the ``parameter_statuses``:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection(
+... "postgres", password="cpsnow", application_name='AGI')
+>>>
+>>> con.parameter_statuses['application_name']
+'AGI'
+>>>
+>>> con.close()
+
+
LIMIT ALL
`````````
@@ -667,7 +685,7 @@ the server:
>>>
>>> con.run("SELECT :v IS NULL", v=None)
Traceback (most recent call last):
-pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': 'exec_parse_message'}
+pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': '...'}
>>>
>>> con.close()
@@ -889,7 +907,7 @@ the ``replication`` keyword when creating a connection:
... 'postgres', password="cpsnow", replication="database")
>>>
>>> con.run("IDENTIFY_SYSTEM")
-[['...', 1, '0/...', 'postgres']]
+[['...', 1, '.../...', 'postgres']]
>>>
>>> con.close()
@@ -2240,6 +2258,14 @@ Run ``tox`` to make sure all tests pass, then update the release notes, then do:
Release Notes
-------------
+Version 1.30.4, 2024-01-03
+``````````````````````````
+
+- Add support for more range and multirange types.
+
+- Make the ``Connection.parameter_statuses`` property a ``dict`` rather than a ``dequeue``.
+
+
Version 1.30.3, 2023-10-31
``````````````````````````
diff --git a/contrib/python/pg8000/README.rst b/contrib/python/pg8000/README.rst
index 71e0fea7511..ad8ea650f27 100644
--- a/contrib/python/pg8000/README.rst
+++ b/contrib/python/pg8000/README.rst
@@ -11,7 +11,7 @@ pg8000
pg8000 is a pure-`Python <https://www.python.org/>`_
`PostgreSQL <http://www.postgresql.org/>`_ driver that complies with
`DB-API 2.0 <http://www.python.org/dev/peps/pep-0249/>`_. It is tested on Python
-versions 3.8+, on CPython and PyPy, and PostgreSQL versions 11+. pg8000's name comes
+versions 3.8+, on CPython and PyPy, and PostgreSQL versions 12+. pg8000's name comes
from the belief that it is probably about the 8000th PostgreSQL interface for Python.
pg8000 is distributed under the BSD 3-clause license.
@@ -275,9 +275,7 @@ PostgreSQL `notices
<https://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html>`_ are
stored in a deque called ``Connection.notices`` and added using the ``append()``
method. Similarly there are ``Connection.notifications`` for `notifications
-<https://www.postgresql.org/docs/current/static/sql-notify.html>`_ and
-``Connection.parameter_statuses`` for changes to the server configuration. Here's an
-example:
+<https://www.postgresql.org/docs/current/static/sql-notify.html>`_. Here's an example:
>>> import pg8000.native
>>>
@@ -293,6 +291,26 @@ example:
>>> con.close()
+Parameter Statuses
+``````````````````
+
+`Certain parameter values are reported by the server automatically at connection startup or whenever
+their values change
+<https://www.postgresql.org/docs/current/libpq-status.html#LIBPQ-PQPARAMETERSTATUS>`_ and pg8000
+stores the latest values in a dict called ``Connection.parameter_statuses``. Here's an example where
+we set the ``aplication_name`` parameter and then read it from the ``parameter_statuses``:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection(
+... "postgres", password="cpsnow", application_name='AGI')
+>>>
+>>> con.parameter_statuses['application_name']
+'AGI'
+>>>
+>>> con.close()
+
+
LIMIT ALL
`````````
@@ -639,7 +657,7 @@ the server:
>>>
>>> con.run("SELECT :v IS NULL", v=None)
Traceback (most recent call last):
-pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': 'exec_parse_message'}
+pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': '...'}
>>>
>>> con.close()
@@ -861,7 +879,7 @@ the ``replication`` keyword when creating a connection:
... 'postgres', password="cpsnow", replication="database")
>>>
>>> con.run("IDENTIFY_SYSTEM")
-[['...', 1, '0/...', 'postgres']]
+[['...', 1, '.../...', 'postgres']]
>>>
>>> con.close()
@@ -2212,6 +2230,14 @@ Run ``tox`` to make sure all tests pass, then update the release notes, then do:
Release Notes
-------------
+Version 1.30.4, 2024-01-03
+``````````````````````````
+
+- Add support for more range and multirange types.
+
+- Make the ``Connection.parameter_statuses`` property a ``dict`` rather than a ``dequeue``.
+
+
Version 1.30.3, 2023-10-31
``````````````````````````
diff --git a/contrib/python/pg8000/pg8000/converters.py b/contrib/python/pg8000/pg8000/converters.py
index 1ce16480ee5..73392a94be6 100644
--- a/contrib/python/pg8000/pg8000/converters.py
+++ b/contrib/python/pg8000/pg8000/converters.py
@@ -40,16 +40,22 @@ CSTRING_ARRAY = 1263
DATE = 1082
DATE_ARRAY = 1182
DATEMULTIRANGE = 4535
+DATEMULTIRANGE_ARRAY = 6155
DATERANGE = 3912
+DATERANGE_ARRAY = 3913
FLOAT = 701
FLOAT_ARRAY = 1022
INET = 869
INET_ARRAY = 1041
INT2VECTOR = 22
INT4MULTIRANGE = 4451
+INT4MULTIRANGE_ARRAY = 6150
INT4RANGE = 3904
+INT4RANGE_ARRAY = 3905
INT8MULTIRANGE = 4536
+INT8MULTIRANGE_ARRAY = 6157
INT8RANGE = 3926
+INT8RANGE_ARRAY = 3927
INTEGER = 23
INTEGER_ARRAY = 1007
INTERVAL = 1186
@@ -67,7 +73,9 @@ NAME_ARRAY = 1003
NUMERIC = 1700
NUMERIC_ARRAY = 1231
NUMRANGE = 3906
+NUMRANGE_ARRAY = 3907
NUMMULTIRANGE = 4532
+NUMMULTIRANGE_ARRAY = 6151
NULLTYPE = -1
OID = 26
POINT = 600
@@ -87,9 +95,13 @@ TIMESTAMP_ARRAY = 1115
TIMESTAMPTZ = 1184
TIMESTAMPTZ_ARRAY = 1185
TSMULTIRANGE = 4533
+TSMULTIRANGE_ARRAY = 6152
TSRANGE = 3908
+TSRANGE_ARRAY = 3909
TSTZMULTIRANGE = 4534
+TSTZMULTIRANGE_ARRAY = 6153
TSTZRANGE = 3910
+TSTZRANGE_ARRAY = 3911
UNKNOWN = 705
UUID_TYPE = 2950
UUID_ARRAY = 2951
@@ -291,6 +303,65 @@ def uuid_in(data):
return UUID(data)
+def _range_in(elem_func):
+ def range_in(data):
+ if data == "empty":
+ return Range(is_empty=True)
+ else:
+ le, ue = [None if v == "" else elem_func(v) for v in data[1:-1].split(",")]
+ return Range(le, ue, bounds=f"{data[0]}{data[-1]}")
+
+ return range_in
+
+
+daterange_in = _range_in(date_in)
+int4range_in = _range_in(int)
+int8range_in = _range_in(int)
+numrange_in = _range_in(Decimal)
+
+
+def ts_in(data):
+ return timestamp_in(data[1:-1])
+
+
+def tstz_in(data):
+ return timestamptz_in(data[1:-1])
+
+
+tsrange_in = _range_in(ts_in)
+tstzrange_in = _range_in(tstz_in)
+
+
+def _multirange_in(adapter):
+ def f(data):
+ in_range = False
+ result = []
+ val = []
+ for c in data:
+ if in_range:
+ val.append(c)
+ if c in "])":
+ value = "".join(val)
+ val.clear()
+ result.append(adapter(value))
+ in_range = False
+ elif c in "[(":
+ val.append(c)
+ in_range = True
+
+ return result
+
+ return f
+
+
+datemultirange_in = _multirange_in(daterange_in)
+int4multirange_in = _multirange_in(int4range_in)
+int8multirange_in = _multirange_in(int8range_in)
+nummultirange_in = _multirange_in(numrange_in)
+tsmultirange_in = _multirange_in(tsrange_in)
+tstzmultirange_in = _multirange_in(tstzrange_in)
+
+
class ParserState(Enum):
InString = 1
InEscape = 2
@@ -353,16 +424,28 @@ bool_array_in = _array_in(bool_in)
bytes_array_in = _array_in(bytes_in)
cidr_array_in = _array_in(cidr_in)
date_array_in = _array_in(date_in)
+datemultirange_array_in = _array_in(datemultirange_in)
+daterange_array_in = _array_in(daterange_in)
inet_array_in = _array_in(inet_in)
int_array_in = _array_in(int)
+int4multirange_array_in = _array_in(int4multirange_in)
+int4range_array_in = _array_in(int4range_in)
+int8multirange_array_in = _array_in(int8multirange_in)
+int8range_array_in = _array_in(int8range_in)
interval_array_in = _array_in(interval_in)
json_array_in = _array_in(json_in)
float_array_in = _array_in(float)
numeric_array_in = _array_in(numeric_in)
+nummultirange_array_in = _array_in(nummultirange_in)
+numrange_array_in = _array_in(numrange_in)
string_array_in = _array_in(string_in)
time_array_in = _array_in(time_in)
timestamp_array_in = _array_in(timestamp_in)
timestamptz_array_in = _array_in(timestamptz_in)
+tsrange_array_in = _array_in(tsrange_in)
+tsmultirange_array_in = _array_in(tsmultirange_in)
+tstzmultirange_array_in = _array_in(tstzmultirange_in)
+tstzrange_array_in = _array_in(tstzrange_in)
uuid_array_in = _array_in(uuid_in)
@@ -443,65 +526,6 @@ def composite_out(ar):
return f'({",".join(result)})'
-def _range_in(elem_func):
- def range_in(data):
- if data == "empty":
- return Range(is_empty=True)
- else:
- le, ue = [None if v == "" else elem_func(v) for v in data[1:-1].split(",")]
- return Range(le, ue, bounds=f"{data[0]}{data[-1]}")
-
- return range_in
-
-
-daterange_in = _range_in(date_in)
-int4range_in = _range_in(int)
-int8range_in = _range_in(int)
-numrange_in = _range_in(Decimal)
-
-
-def ts_in(data):
- return timestamp_in(data[1:-1])
-
-
-def tstz_in(data):
- return timestamptz_in(data[1:-1])
-
-
-tsrange_in = _range_in(ts_in)
-tstzrange_in = _range_in(tstz_in)
-
-
-def _multirange_in(adapter):
- def f(data):
- in_range = False
- result = []
- val = []
- for c in data:
- if in_range:
- val.append(c)
- if c in "])":
- value = "".join(val)
- val.clear()
- result.append(adapter(value))
- in_range = False
- elif c in "[(":
- val.append(c)
- in_range = True
-
- return result
-
- return f
-
-
-datemultirange_in = _multirange_in(daterange_in)
-int4multirange_in = _multirange_in(int4range_in)
-int8multirange_in = _multirange_in(int8range_in)
-nummultirange_in = _multirange_in(numrange_in)
-tsmultirange_in = _multirange_in(tsrange_in)
-tstzmultirange_in = _multirange_in(tstzrange_in)
-
-
def record_in(data):
state = ParserState.Out
results = []
@@ -605,15 +629,21 @@ PG_TYPES = {
DATE: date_in, # date
DATE_ARRAY: date_array_in, # date[]
DATEMULTIRANGE: datemultirange_in, # datemultirange
+ DATEMULTIRANGE_ARRAY: datemultirange_array_in, # datemultirange[]
DATERANGE: daterange_in, # daterange
+ DATERANGE_ARRAY: daterange_array_in, # daterange[]
FLOAT: float, # float8
FLOAT_ARRAY: float_array_in, # float8[]
INET: inet_in, # inet
INET_ARRAY: inet_array_in, # inet[]
INT4MULTIRANGE: int4multirange_in, # int4multirange
+ INT4MULTIRANGE_ARRAY: int4multirange_array_in, # int4multirange[]
INT4RANGE: int4range_in, # int4range
+ INT4RANGE_ARRAY: int4range_array_in, # int4range[]
INT8MULTIRANGE: int8multirange_in, # int8multirange
+ INT8MULTIRANGE_ARRAY: int8multirange_array_in, # int8multirange[]
INT8RANGE: int8range_in, # int8range
+ INT8RANGE_ARRAY: int8range_array_in, # int8range[]
INTEGER: int, # int4
INTEGER_ARRAY: int_array_in, # int4[]
JSON: json_in, # json
@@ -628,7 +658,9 @@ PG_TYPES = {
NUMERIC: numeric_in, # numeric
NUMERIC_ARRAY: numeric_array_in, # numeric[]
NUMRANGE: numrange_in, # numrange
+ NUMRANGE_ARRAY: numrange_array_in, # numrange[]
NUMMULTIRANGE: nummultirange_in, # nummultirange
+ NUMMULTIRANGE_ARRAY: nummultirange_array_in, # nummultirange[]
OID: int, # oid
POINT: point_in, # point
INTERVAL: interval_in, # interval
@@ -649,9 +681,13 @@ PG_TYPES = {
TIMESTAMPTZ: timestamptz_in, # timestamptz
TIMESTAMPTZ_ARRAY: timestamptz_array_in, # timestamptz
TSMULTIRANGE: tsmultirange_in, # tsmultirange
+ TSMULTIRANGE_ARRAY: tsmultirange_array_in, # tsmultirange[]
TSRANGE: tsrange_in, # tsrange
+ TSRANGE_ARRAY: tsrange_array_in, # tsrange[]
TSTZMULTIRANGE: tstzmultirange_in, # tstzmultirange
+ TSTZMULTIRANGE_ARRAY: tstzmultirange_array_in, # tstzmultirange[]
TSTZRANGE: tstzrange_in, # tstzrange
+ TSTZRANGE_ARRAY: tstzrange_array_in, # tstzrange[]
UNKNOWN: string_in, # unknown
UUID_ARRAY: uuid_array_in, # uuid[]
UUID_TYPE: uuid_in, # uuid
diff --git a/contrib/python/pg8000/pg8000/core.py b/contrib/python/pg8000/pg8000/core.py
index 54ef496ddd9..3e55957755a 100644
--- a/contrib/python/pg8000/pg8000/core.py
+++ b/contrib/python/pg8000/pg8000/core.py
@@ -277,7 +277,7 @@ class CoreConnection:
)
self.notifications = deque(maxlen=100)
self.notices = deque(maxlen=100)
- self.parameter_statuses = deque(maxlen=100)
+ self.parameter_statuses = {}
if user is None:
raise InterfaceError("The 'user' connection parameter cannot be None")
@@ -842,20 +842,20 @@ class CoreConnection:
def handle_PARAMETER_STATUS(self, data, context):
pos = data.find(NULL_BYTE)
- key, value = data[:pos], data[pos + 1 : -1]
- self.parameter_statuses.append((key, value))
- if key == b"client_encoding":
- encoding = value.decode("ascii").lower()
+ key, value = data[:pos].decode("ascii"), data[pos + 1 : -1].decode("ascii")
+ self.parameter_statuses[key] = value
+ if key == "client_encoding":
+ encoding = value.lower()
self._client_encoding = PG_PY_ENCODINGS.get(encoding, encoding)
- elif key == b"integer_datetimes":
- if value == b"on":
+ elif key == "integer_datetimes":
+ if value == "on":
pass
else:
pass
- elif key == b"server_version":
+ elif key == "server_version":
pass
diff --git a/contrib/python/pg8000/ya.make b/contrib/python/pg8000/ya.make
index 6172a193123..50676227044 100644
--- a/contrib/python/pg8000/ya.make
+++ b/contrib/python/pg8000/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(1.30.3)
+VERSION(1.30.4)
LICENSE(BSD-3-Clause)
diff --git a/contrib/python/traitlets/py3/.dist-info/METADATA b/contrib/python/traitlets/py3/.dist-info/METADATA
index 527825535af..5e9a9e15188 100644
--- a/contrib/python/traitlets/py3/.dist-info/METADATA
+++ b/contrib/python/traitlets/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: traitlets
-Version: 5.14.0
+Version: 5.14.1
Summary: Traitlets Python configuration system
Project-URL: Homepage, https://github.com/ipython/traitlets
Project-URL: Documentation, https://traitlets.readthedocs.io
diff --git a/contrib/python/traitlets/py3/tests/_warnings.py b/contrib/python/traitlets/py3/tests/_warnings.py
index 1fb6e9a9a57..3447fbef8a6 100644
--- a/contrib/python/traitlets/py3/tests/_warnings.py
+++ b/contrib/python/traitlets/py3/tests/_warnings.py
@@ -1,5 +1,6 @@
# From scikit-image: https://github.com/scikit-image/scikit-image/blob/c2f8c4ab123ebe5f7b827bc495625a32bb225c10/skimage/_shared/_warnings.py
# Licensed under modified BSD license
+from __future__ import annotations
__all__ = ["all_warnings", "expected_warnings"]
diff --git a/contrib/python/traitlets/py3/tests/config/test_application.py b/contrib/python/traitlets/py3/tests/config/test_application.py
index 61ad751c6ba..73d31432a4e 100644
--- a/contrib/python/traitlets/py3/tests/config/test_application.py
+++ b/contrib/python/traitlets/py3/tests/config/test_application.py
@@ -4,6 +4,7 @@ Tests for traitlets.config.application.Application
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
import contextlib
import io
@@ -17,7 +18,6 @@ from tempfile import TemporaryDirectory
from unittest import TestCase, mock
import pytest
-from pytest import mark
from traitlets import Bool, Bytes, Dict, HasTraits, Integer, List, Set, Tuple, Unicode
from traitlets.config.application import Application
@@ -551,7 +551,7 @@ class TestApplication(TestCase):
app.init_bar()
self.assertEqual(app.bar.b, 1)
- @mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
+ @pytest.mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
def test_log_collisions(self):
app = MyApp()
app.log = logging.getLogger()
@@ -572,7 +572,7 @@ class TestApplication(TestCase):
assert pjoin(td, name + ".py") in output
assert pjoin(td, name + ".json") in output
- @mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
+ @pytest.mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
def test_log_bad_config(self):
app = MyApp()
app.log = logging.getLogger()
@@ -670,7 +670,7 @@ class TestApplication(TestCase):
self.assertEqual(app.running, False)
def test_cli_multi_scalar(caplog):
class App(Application):
aliases = {"opt": "App.opt"}
@@ -855,7 +855,7 @@ def test_get_default_logging_config_pythonw(monkeypatch):
assert "loggers" in config
def caplogconfig(monkeypatch):
"""Capture logging config events for DictConfigurator objects.
diff --git a/contrib/python/traitlets/py3/tests/config/test_argcomplete.py b/contrib/python/traitlets/py3/tests/config/test_argcomplete.py
index 0cd992c6125..3f1ce482e88 100644
--- a/contrib/python/traitlets/py3/tests/config/test_argcomplete.py
+++ b/contrib/python/traitlets/py3/tests/config/test_argcomplete.py
@@ -4,6 +4,7 @@ Tests for argcomplete handling by traitlets.config.application.Application
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
import io
import os
@@ -71,7 +72,7 @@ class TestArgcomplete:
IFS = "\013"
COMP_WORDBREAKS = " \t\n\"'><=;|&(:"
- @pytest.fixture
+ @pytest.fixture()
def argcomplete_on(self, mocker):
"""Mostly borrowed from argcomplete's unit test fixtures
@@ -119,7 +120,7 @@ class TestArgcomplete:
os.environ["COMP_LINE"] = command
os.environ["COMP_POINT"] = str(point)
- with pytest.raises(CustomError) as cm:
+ with pytest.raises(CustomError) as cm: # noqa: PT012
app.argcomplete_kwargs = dict(
output_stream=strio, exit_method=CustomError.exit, **kwargs
)
@@ -216,4 +217,5 @@ class TestArgcomplete:
app = MainApp()
completions = set(self.run_completer(app, "app --"))
assert completions > {"--Application.", "--MainApp."}
- assert "--SubApp1." not in completions and "--SubApp2." not in completions
+ assert "--SubApp1." not in completions
+ assert "--SubApp2." not in completions
diff --git a/contrib/python/traitlets/py3/tests/config/test_configurable.py b/contrib/python/traitlets/py3/tests/config/test_configurable.py
index f6499ea29d1..f1e8ed74a38 100644
--- a/contrib/python/traitlets/py3/tests/config/test_configurable.py
+++ b/contrib/python/traitlets/py3/tests/config/test_configurable.py
@@ -2,11 +2,12 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
import logging
from unittest import TestCase
-from pytest import mark
+import pytest
from .._warnings import expected_warnings
from traitlets.config.application import Application
@@ -672,7 +673,7 @@ class TestLogger(TestCase):
bar = Integer(config=True)
baz = Integer(config=True)
- @mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
+ @pytest.mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
def test_warn_match(self):
logger = logging.getLogger("test_warn_match")
cfg = Config({"A": {"bat": 5}})
diff --git a/contrib/python/traitlets/py3/tests/config/test_loader.py b/contrib/python/traitlets/py3/tests/config/test_loader.py
index 9d864317bcd..6e1510c2cd1 100644
--- a/contrib/python/traitlets/py3/tests/config/test_loader.py
+++ b/contrib/python/traitlets/py3/tests/config/test_loader.py
@@ -2,6 +2,7 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
import copy
import os
@@ -123,9 +124,8 @@ class TestFileCL(TestCase):
with JSONFileConfigLoader(fname, log=log) as config:
config.A.b = 1
- with self.assertRaises(TypeError):
- with JSONFileConfigLoader(fname, log=log) as config:
- config.A.cant_json = lambda x: x
+ with self.assertRaises(TypeError), JSONFileConfigLoader(fname, log=log) as config:
+ config.A.cant_json = lambda x: x
loader = JSONFileConfigLoader(fname, log=log)
cfg = loader.load_config()
diff --git a/contrib/python/traitlets/py3/tests/test_traitlets.py b/contrib/python/traitlets/py3/tests/test_traitlets.py
index 07c95ca2519..dfcf3f0f4b5 100644
--- a/contrib/python/traitlets/py3/tests/test_traitlets.py
+++ b/contrib/python/traitlets/py3/tests/test_traitlets.py
@@ -5,6 +5,7 @@
#
# Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
# also under the terms of the Modified BSD License.
+from __future__ import annotations
import pickle
import re
@@ -1254,7 +1255,7 @@ class TraitTestBase(TestCase):
try:
self.assertRaises(TraitError, self.assign, value)
except AssertionError:
- assert False, value
+ assert False, value # noqa: PT015
def test_default_value(self):
if hasattr(self, "_default_value"):
@@ -1784,7 +1785,7 @@ class TestMultiTuple(TraitTestBase):
@pytest.mark.parametrize(
"Trait",
- (
+ ( # noqa: PT007
List,
Tuple,
Set,
@@ -1808,7 +1809,7 @@ def test_allow_none_default_value(Trait):
@pytest.mark.parametrize(
"Trait, default_value",
- ((List, []), (Tuple, ()), (Set, set()), (Dict, {}), (Integer, 0), (Unicode, "")),
+ ((List, []), (Tuple, ()), (Set, set()), (Dict, {}), (Integer, 0), (Unicode, "")), # noqa: PT007
)
def test_default_value(Trait, default_value):
class C(HasTraits):
@@ -1822,7 +1823,7 @@ def test_default_value(Trait, default_value):
@pytest.mark.parametrize(
"Trait, default_value",
- ((List, []), (Tuple, ()), (Set, set())),
+ ((List, []), (Tuple, ()), (Set, set())), # noqa: PT007
)
def test_subclass_default_value(Trait, default_value):
"""Test deprecated default_value=None behavior for Container subclass traits"""
@@ -2150,7 +2151,7 @@ class TestLink(TestCase):
self.i = change.new * 2
mc = MyClass()
- l = link((mc, "i"), (mc, "j")) # noqa
+ l = link((mc, "i"), (mc, "j")) # noqa: E741
self.assertRaises(TraitError, setattr, mc, "i", 2)
def test_link_broken_at_target(self):
@@ -2163,7 +2164,7 @@ class TestLink(TestCase):
self.j = change.new * 2
mc = MyClass()
- l = link((mc, "i"), (mc, "j")) # noqa
+ l = link((mc, "i"), (mc, "j")) # noqa: E741
self.assertRaises(TraitError, setattr, mc, "j", 2)
@@ -2393,7 +2394,7 @@ class OrderTraits(HasTraits):
i = Unicode()
j = Unicode()
k = Unicode()
- l = Unicode() # noqa
+ l = Unicode() # noqa: E741
def _notify(self, name, old, new):
"""check the value of all traits when each trait change is triggered
@@ -2819,7 +2820,7 @@ def test_default_mro():
def test_cls_self_argument():
class X(HasTraits):
- def __init__(__self, cls, self): # noqa
+ def __init__(__self, cls, self):
pass
x = X(cls=None, self=None)
@@ -2889,7 +2890,7 @@ def _from_string_test(traittype, s, expected):
else:
cast = trait.from_string
if type(expected) is type and issubclass(expected, Exception):
- with pytest.raises(expected):
+ with pytest.raises(expected): # noqa: PT012
value = cast(s)
trait.validate(CrossValidationStub(), value) # type:ignore
else:
diff --git a/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py b/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py
index 700199108f1..685ea71f695 100644
--- a/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py
+++ b/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py
@@ -3,6 +3,8 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
#
+from __future__ import annotations
+
from traitlets import Dict, Instance, Integer, Unicode, Union
from traitlets.config import Configurable
diff --git a/contrib/python/traitlets/py3/tests/test_traitlets_enum.py b/contrib/python/traitlets/py3/tests/test_traitlets_enum.py
index c39007e8a05..ac19e9d519f 100644
--- a/contrib/python/traitlets/py3/tests/test_traitlets_enum.py
+++ b/contrib/python/traitlets/py3/tests/test_traitlets_enum.py
@@ -2,6 +2,7 @@
"""
Test the trait-type ``UseEnum``.
"""
+from __future__ import annotations
import enum
import unittest
@@ -280,7 +281,7 @@ class TestFuzzyEnum(unittest.TestCase):
example = FuzzyExample()
for color in color_choices:
- for wlen in range(0, 2):
+ for wlen in range(2):
value = color[wlen:]
example.color = value
diff --git a/contrib/python/traitlets/py3/tests/utils/test_bunch.py b/contrib/python/traitlets/py3/tests/utils/test_bunch.py
index 223124d7d5e..90efe982739 100644
--- a/contrib/python/traitlets/py3/tests/utils/test_bunch.py
+++ b/contrib/python/traitlets/py3/tests/utils/test_bunch.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from traitlets.utils.bunch import Bunch
diff --git a/contrib/python/traitlets/py3/tests/utils/test_decorators.py b/contrib/python/traitlets/py3/tests/utils/test_decorators.py
index d6bf8414e5a..39b882c0ea8 100644
--- a/contrib/python/traitlets/py3/tests/utils/test_decorators.py
+++ b/contrib/python/traitlets/py3/tests/utils/test_decorators.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from inspect import Parameter, signature
from unittest import TestCase
diff --git a/contrib/python/traitlets/py3/tests/utils/test_importstring.py b/contrib/python/traitlets/py3/tests/utils/test_importstring.py
index 8ce28add41e..43fcdaaff2b 100644
--- a/contrib/python/traitlets/py3/tests/utils/test_importstring.py
+++ b/contrib/python/traitlets/py3/tests/utils/test_importstring.py
@@ -4,6 +4,7 @@
# Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
# also under the terms of the Modified BSD License.
"""Tests for traitlets.utils.importstring."""
+from __future__ import annotations
import os
from unittest import TestCase
diff --git a/contrib/python/traitlets/py3/traitlets/__init__.py b/contrib/python/traitlets/py3/traitlets/__init__.py
index 70ed818aca3..e27ecf1a451 100644
--- a/contrib/python/traitlets/py3/traitlets/__init__.py
+++ b/contrib/python/traitlets/py3/traitlets/__init__.py
@@ -1,4 +1,6 @@
"""Traitlets Python configuration system"""
+from __future__ import annotations
+
import typing as _t
from . import traitlets
diff --git a/contrib/python/traitlets/py3/traitlets/_version.py b/contrib/python/traitlets/py3/traitlets/_version.py
index 7a5c6e10050..08221fa99fb 100644
--- a/contrib/python/traitlets/py3/traitlets/_version.py
+++ b/contrib/python/traitlets/py3/traitlets/_version.py
@@ -1,11 +1,13 @@
"""
handle the current version info of traitlets.
"""
+from __future__ import annotations
+
import re
from typing import List
# Version string must appear intact for hatch versioning
-__version__ = "5.14.0"
+__version__ = "5.14.1"
# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
diff --git a/contrib/python/traitlets/py3/traitlets/config/__init__.py b/contrib/python/traitlets/py3/traitlets/config/__init__.py
index 699b12b80aa..e51a4219a01 100644
--- a/contrib/python/traitlets/py3/traitlets/config/__init__.py
+++ b/contrib/python/traitlets/py3/traitlets/config/__init__.py
@@ -1,16 +1,18 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
from .application import *
from .configurable import *
from .loader import Config
-__all__ = [ # noqa
+__all__ = [ # noqa: F405
"Config",
"Application",
"ApplicationError",
"LevelFormatter",
"configurable",
+ "Configurable",
"ConfigurableError",
"MultipleInstanceError",
"LoggingConfigurable",
diff --git a/contrib/python/traitlets/py3/traitlets/config/application.py b/contrib/python/traitlets/py3/traitlets/config/application.py
index 9a3fcc949ef..b01a11e518d 100644
--- a/contrib/python/traitlets/py3/traitlets/config/application.py
+++ b/contrib/python/traitlets/py3/traitlets/config/application.py
@@ -304,8 +304,7 @@ class Application(SingletonConfigurable):
return log
if not _log.propagate:
break
- else:
- _log = _log.parent # type:ignore[assignment]
+ _log = _log.parent # type:ignore[assignment]
return log
logging_config = Dict(
@@ -501,7 +500,7 @@ class Application(SingletonConfigurable):
if not class_config:
continue
print(classname)
- pformat_kwargs: StrDict = dict(indent=4, compact=True)
+ pformat_kwargs: StrDict = dict(indent=4, compact=True) # noqa: C408
for traitname in sorted(class_config):
value = class_config[traitname]
@@ -924,7 +923,7 @@ class Application(SingletonConfigurable):
if raise_config_file_errors:
raise
if log:
- log.error("Exception while loading config file %s", filename, exc_info=True)
+ log.error("Exception while loading config file %s", filename, exc_info=True) # noqa: G201
else:
if log:
log.debug("Loaded config file: %s", loader.full_filename)
@@ -933,7 +932,7 @@ class Application(SingletonConfigurable):
collisions = earlier_config.collisions(config)
if collisions and log:
log.warning(
- "Collisions detected in {0} and {1} config files."
+ "Collisions detected in {0} and {1} config files." # noqa: G001
" {1} has higher priority: {2}".format(
filename,
loader.full_filename,
@@ -974,8 +973,7 @@ class Application(SingletonConfigurable):
@catch_config_error
def load_config_environ(self) -> None:
"""Load config files by environment."""
-
- PREFIX = self.name.upper()
+ PREFIX = self.name.upper().replace("-", "_")
new_config = Config()
self.log.debug('Looping through config variables with prefix "%s"', PREFIX)
@@ -1059,7 +1057,7 @@ class Application(SingletonConfigurable):
self._logging_configured = False
def exit(self, exit_status: int | str | None = 0) -> None:
- self.log.debug("Exiting application: %s" % self.name)
+ self.log.debug("Exiting application: %s", self.name)
self.close_handlers()
sys.exit(exit_status)
diff --git a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py
index 6c04fd56756..1f4cf1c7875 100644
--- a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py
+++ b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py
@@ -2,7 +2,7 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
-
+from __future__ import annotations
import argparse
import os
@@ -58,7 +58,7 @@ def get_argcomplete_cwords() -> t.Optional[t.List[str]]:
comp_words = comp_words[start:]
# argcomplete.debug("prequote=", cword_prequote, "prefix=", cword_prefix, "suffix=", cword_suffix, "words=", comp_words, "last=", last_wordbreak_pos)
- return comp_words
+ return comp_words # noqa: RET504
def increment_argcomplete_index() -> None:
diff --git a/contrib/python/traitlets/py3/traitlets/config/configurable.py b/contrib/python/traitlets/py3/traitlets/config/configurable.py
index 3e8c868ce02..44b4793ebd5 100644
--- a/contrib/python/traitlets/py3/traitlets/config/configurable.py
+++ b/contrib/python/traitlets/py3/traitlets/config/configurable.py
@@ -324,7 +324,7 @@ class Configurable(HasTraits):
@classmethod
def class_print_help(cls, inst: HasTraits | None = None) -> None:
"""Get the help string for a single trait and print it."""
- print(cls.class_get_help(inst))
+ print(cls.class_get_help(inst)) # noqa: T201
@classmethod
def _defining_class(
diff --git a/contrib/python/traitlets/py3/traitlets/config/loader.py b/contrib/python/traitlets/py3/traitlets/config/loader.py
index 2b0932a98e7..f9eb5fe191e 100644
--- a/contrib/python/traitlets/py3/traitlets/config/loader.py
+++ b/contrib/python/traitlets/py3/traitlets/config/loader.py
@@ -31,7 +31,7 @@ class ConfigLoaderError(ConfigError):
pass
-class ConfigFileNotFound(ConfigError): # noqa
+class ConfigFileNotFound(ConfigError):
pass
@@ -79,7 +79,7 @@ class ArgumentParser(argparse.ArgumentParser):
def execfile(fname: str, glob: dict[str, Any]) -> None:
with open(fname, "rb") as f:
- exec(compile(f.read(), fname, "exec"), glob, glob) # noqa
+ exec(compile(f.read(), fname, "exec"), glob, glob) # noqa: S102
class LazyConfigValue(HasTraits):
@@ -218,10 +218,7 @@ class LazyConfigValue(HasTraits):
def _is_section_key(key: str) -> bool:
"""Is a Config key a section name (does it start with a capital)?"""
- if key and key[0].upper() == key[0] and not key.startswith("_"):
- return True
- else:
- return False
+ return bool(key and key[0].upper() == key[0] and not key.startswith("_"))
class Config(dict): # type:ignore[type-arg]
@@ -382,8 +379,6 @@ class Config(dict): # type:ignore[type-arg]
class DeferredConfig:
"""Class for deferred-evaluation of config from CLI"""
- pass
-
def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any:
raise NotImplementedError("Implement in subclasses")
@@ -597,7 +592,7 @@ class JSONFileConfigLoader(FileConfigLoader):
self.load_config()
return self.config
- def __exit__(self, exc_type: t.Any, exc_value: t.Any, traceback: t.Any) -> None:
+ def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None:
"""
Exit the context manager but do not handle any errors.
@@ -649,7 +644,7 @@ class PyFileConfigLoader(FileConfigLoader):
"""Unnecessary now, but a deprecation warning is more trouble than it's worth."""
return self.config
- namespace = dict(
+ namespace = dict( # noqa: C408
c=self.config,
load_subconfig=self.load_subconfig,
get_config=get_config,
@@ -657,7 +652,7 @@ class PyFileConfigLoader(FileConfigLoader):
)
conf_filename = self.full_filename
with open(conf_filename, "rb") as f:
- exec(compile(f.read(), conf_filename, "exec"), namespace, namespace) # noqa
+ exec(compile(f.read(), conf_filename, "exec"), namespace, namespace) # noqa: S102
class CommandLineConfigLoader(ConfigLoader):
@@ -856,7 +851,7 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
self.parser_args = parser_args
self.version = parser_kw.pop("version", None)
- kwargs = dict(argument_default=argparse.SUPPRESS)
+ kwargs = dict(argument_default=argparse.SUPPRESS) # noqa: C408
kwargs.update(parser_kw)
self.parser_kw = kwargs
@@ -919,7 +914,6 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
"""If argcomplete is enabled, allow triggering command-line autocompletion"""
- pass
def _parse_args(self, args: t.Any) -> t.Any:
"""self.parser->self.parsed_data"""
@@ -1089,7 +1083,7 @@ class KVArgParseConfigLoader(ArgParseConfigLoader):
if lhs == "extra_args":
self.extra_args = ["-" if a == _DASH_REPLACEMENT else a for a in rhs] + extra_args
continue
- elif lhs == "_flags":
+ if lhs == "_flags":
# _flags will be handled later
continue
@@ -1132,7 +1126,7 @@ class KVArgParseConfigLoader(ArgParseConfigLoader):
def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
"""If argcomplete is enabled, allow triggering command-line autocompletion"""
try:
- import argcomplete # noqa
+ import argcomplete # noqa: F401
except ImportError:
return
diff --git a/contrib/python/traitlets/py3/traitlets/config/manager.py b/contrib/python/traitlets/py3/traitlets/config/manager.py
index 9102544e509..f87b21e782f 100644
--- a/contrib/python/traitlets/py3/traitlets/config/manager.py
+++ b/contrib/python/traitlets/py3/traitlets/config/manager.py
@@ -70,8 +70,7 @@ class BaseJSONConfigManager(LoggingConfigurable):
filename = self.file_name(section_name)
self.ensure_config_dir_exists()
- f = open(filename, "w", encoding="utf-8")
- with f:
+ with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def update(self, section_name: str, new_data: Any) -> Any:
diff --git a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py
index 635b6bdfa36..e4708a22706 100644
--- a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py
+++ b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py
@@ -50,8 +50,7 @@ def setup(app: t.Any) -> dict[str, t.Any]:
module instead.
"""
app.add_object_type("configtrait", "configtrait", objname="Config option")
- metadata = {"parallel_read_safe": True, "parallel_write_safe": True}
- return metadata
+ return {"parallel_read_safe": True, "parallel_write_safe": True}
def interesting_default_value(dv: t.Any) -> bool:
diff --git a/contrib/python/traitlets/py3/traitlets/log.py b/contrib/python/traitlets/py3/traitlets/log.py
index d90a9c5284c..112b2004426 100644
--- a/contrib/python/traitlets/py3/traitlets/log.py
+++ b/contrib/python/traitlets/py3/traitlets/log.py
@@ -16,7 +16,7 @@ def get_logger() -> logging.Logger | logging.LoggerAdapter[Any]:
If a global Application is instantiated, grab its logger.
Otherwise, grab the root logger.
"""
- global _logger
+ global _logger # noqa: PLW0603
if _logger is None:
from .config import Application
diff --git a/contrib/python/traitlets/py3/traitlets/tests/utils.py b/contrib/python/traitlets/py3/traitlets/tests/utils.py
index 9552a5c786c..254d46a758a 100644
--- a/contrib/python/traitlets/py3/traitlets/tests/utils.py
+++ b/contrib/python/traitlets/py3/traitlets/tests/utils.py
@@ -10,7 +10,7 @@ def get_output_error_code(cmd: str | Sequence[str]) -> tuple[str, str, Any]:
import os
env = os.environ.copy()
env["Y_PYTHON_ENTRY_POINT"] = ":main"
- p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) # noqa
+ p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) # noqa: S603
out, err = p.communicate()
out_str = out.decode("utf8", "replace")
err_str = err.decode("utf8", "replace")
diff --git a/contrib/python/traitlets/py3/traitlets/traitlets.py b/contrib/python/traitlets/py3/traitlets/traitlets.py
index b3657ab9509..1d1675ab0cf 100644
--- a/contrib/python/traitlets/py3/traitlets/traitlets.py
+++ b/contrib/python/traitlets/py3/traitlets/traitlets.py
@@ -476,7 +476,6 @@ class BaseDescriptor:
:meth:`BaseDescriptor.instance_init` method of descriptors holding
other descriptors.
"""
- pass
G = TypeVar("G")
@@ -714,8 +713,7 @@ class TraitType(BaseDescriptor, t.Generic[G, S]):
"""
if self.read_only:
raise TraitError('The "%s" trait is read-only.' % self.name)
- else:
- self.set(obj, value)
+ self.set(obj, value)
def _validate(self, obj: t.Any, value: t.Any) -> G | None:
if value is None and self.allow_none:
@@ -810,27 +808,27 @@ class TraitType(BaseDescriptor, t.Generic[G, S]):
),
)
raise error
+
+ # this trait caused an error
+ if self.name is None:
+ # this is not the root trait
+ raise TraitError(value, info or self.info(), self)
+
+ # this is the root trait
+ if obj is not None:
+ e = "The '{}' trait of {} instance expected {}, not {}.".format(
+ self.name,
+ class_of(obj),
+ info or self.info(),
+ describe("the", value),
+ )
else:
- # this trait caused an error
- if self.name is None:
- # this is not the root trait
- raise TraitError(value, info or self.info(), self)
- else:
- # this is the root trait
- if obj is not None:
- e = "The '{}' trait of {} instance expected {}, not {}.".format(
- self.name,
- class_of(obj),
- info or self.info(),
- describe("the", value),
- )
- else:
- e = "The '{}' trait expected {}, not {}.".format(
- self.name,
- info or self.info(),
- describe("the", value),
- )
- raise TraitError(e)
+ e = "The '{}' trait expected {}, not {}.".format(
+ self.name,
+ info or self.info(),
+ describe("the", value),
+ )
+ raise TraitError(e)
def get_metadata(self, key: str, default: t.Any = None) -> t.Any:
"""DEPRECATED: Get a metadata value.
@@ -905,7 +903,7 @@ class _CallbackWrapper:
if self.nargs > 4:
raise TraitError("a trait changed callback must have 0-4 arguments.")
- def __eq__(self, other: t.Any) -> bool:
+ def __eq__(self, other: object) -> bool:
# The wrapper is equal to the wrapped element
if isinstance(other, _CallbackWrapper):
return bool(self.cb == other.cb)
@@ -941,7 +939,7 @@ class MetaHasDescriptors(type):
"""
def __new__(
- mcls: type[MetaHasDescriptors], # noqa: N804
+ mcls: type[MetaHasDescriptors],
name: str,
bases: tuple[type, ...],
classdict: dict[str, t.Any],
@@ -993,7 +991,7 @@ class MetaHasDescriptors(type):
class MetaHasTraits(MetaHasDescriptors):
"""A metaclass for HasTraits."""
- def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None: # noqa
+ def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None:
# for only the current class
cls._trait_default_generators: dict[str, t.Any] = {}
# also looking at base classes
@@ -1490,7 +1488,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
self.set_trait(name, value)
except TraitError as e:
# Roll back in case of TraitError during final cross validation.
- self.notify_change = lambda x: None # type:ignore[method-assign, assignment]
+ self.notify_change = lambda x: None # type:ignore[method-assign, assignment] # noqa: ARG005
for name, changes in cache.items():
for change in changes[::-1]:
# TODO: Separate in a rollback function per notification type.
@@ -1763,8 +1761,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
cls = self.__class__
if not self.has_trait(name):
raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
- else:
- getattr(cls, name).set(self, value)
+ getattr(cls, name).set(self, value)
@classmethod
def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]:
@@ -2363,16 +2360,12 @@ class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
Forward-declared version of Type.
"""
- pass
-
class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
"""
Forward-declared version of Instance.
"""
- pass
-
class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]):
"""A trait for instances of the class containing this trait.
@@ -4010,8 +4003,7 @@ class Dict(Instance["dict[K, V]"]):
value = super().validate(obj, value)
if value is None:
return value
- value_dict = self.validate_elements(obj, value)
- return value_dict
+ return self.validate_elements(obj, value)
def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V] | None:
per_key_override = self._per_key_traits or {}
diff --git a/contrib/python/traitlets/py3/traitlets/utils/decorators.py b/contrib/python/traitlets/py3/traitlets/utils/decorators.py
index 8090636bc82..5b77d701dee 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/decorators.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/decorators.py
@@ -1,4 +1,5 @@
"""Useful decorators for Traitlets users."""
+from __future__ import annotations
import copy
from inspect import Parameter, Signature, signature
diff --git a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py
index 785ec581164..703037163a5 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py
@@ -179,5 +179,4 @@ def repr_type(obj: Any) -> str:
error messages.
"""
the_type = type(obj)
- msg = f"{obj!r} {the_type!r}"
- return msg
+ return f"{obj!r} {the_type!r}"
diff --git a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py
index 7cbc82659c4..72c9120cb90 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py
@@ -7,6 +7,8 @@
:copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+from __future__ import annotations
+
import inspect
from functools import partial
from typing import Any
diff --git a/contrib/python/traitlets/py3/traitlets/utils/importstring.py b/contrib/python/traitlets/py3/traitlets/utils/importstring.py
index 413c2033c17..203f79f0547 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/importstring.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/importstring.py
@@ -3,6 +3,8 @@ A simple utility to import something by its string name.
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
+
from typing import Any
diff --git a/contrib/python/traitlets/py3/traitlets/utils/nested_update.py b/contrib/python/traitlets/py3/traitlets/utils/nested_update.py
index 37e2d27cd20..33a5ab8a77b 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/nested_update.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/nested_update.py
@@ -1,5 +1,7 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
+from __future__ import annotations
+
from typing import Any, Dict
diff --git a/contrib/python/traitlets/py3/traitlets/utils/text.py b/contrib/python/traitlets/py3/traitlets/utils/text.py
index 72ad98fc2ae..1c1ac208150 100644
--- a/contrib/python/traitlets/py3/traitlets/utils/text.py
+++ b/contrib/python/traitlets/py3/traitlets/utils/text.py
@@ -1,6 +1,7 @@
"""
Utilities imported from ipython_genutils
"""
+from __future__ import annotations
import re
import textwrap
@@ -10,8 +11,7 @@ from typing import List
def indent(val: str) -> str:
- res = _indent(val, " ")
- return res
+ return _indent(val, " ")
def wrap_paragraphs(text: str, ncols: int = 80) -> List[str]:
diff --git a/contrib/python/traitlets/py3/ya.make b/contrib/python/traitlets/py3/ya.make
index 23849901128..fffcd05afdd 100644
--- a/contrib/python/traitlets/py3/ya.make
+++ b/contrib/python/traitlets/py3/ya.make
@@ -4,7 +4,7 @@ PY3_LIBRARY()
PROVIDES(python_traitlets)
-VERSION(5.14.0)
+VERSION(5.14.1)
LICENSE(BSD-3-Clause)