diff options
author | armenqa <[email protected]> | 2024-01-19 12:23:50 +0300 |
---|---|---|
committer | armenqa <[email protected]> | 2024-01-19 13:10:03 +0300 |
commit | 2de0149d0151c514b22bca0760b95b26c9b0b578 (patch) | |
tree | 2bfed9f3bce7e643ddf048bb61ce3dc0a714bcc2 /contrib/python | |
parent | a8c06d218f12b2406fbce24d194885c5d7b68503 (diff) |
feat contrib: aiogram 3
Relates: https://st.yandex-team.ru/, https://st.yandex-team.ru/
Diffstat (limited to 'contrib/python')
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) |