Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-keyring for openSUSE:Factory checked in at 2025-09-30 17:34:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-keyring (Old) and /work/SRC/openSUSE:Factory/.python-keyring.new.11973 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-keyring" Tue Sep 30 17:34:46 2025 rev:63 rq:1307742 version:25.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-keyring/python-keyring.changes 2025-08-22 17:47:13.570558855 +0200 +++ /work/SRC/openSUSE:Factory/.python-keyring.new.11973/python-keyring.changes 2025-09-30 17:35:09.063007097 +0200 @@ -1,0 +2,23 @@ +Mon Sep 29 10:42:21 UTC 2025 - Dirk Müller <[email protected]> + +- update to 25.6.0: + * Avoid logging a warning when config does not specify a + backend. + * When parsing keyring_path from the config, the home directory + is now expanded from ~. + * In get_credential, now returns None when the indicated + username is not found. + * Fixed ValueError for AnonymousCredentials in CLI. + * Refined type spec and interfaces on credential objects. + Introduced AnonymousCredential to model a secret without a + username. + * Deprecated support for empty usernames. Now all backends will + reject an empty string as input for the 'username' field when + setting a password. Later this deprecation will become a more + visible user warning and even later an error. If this warning + is triggered in your environment, please consider using a + static value (even 'username') or comment in the issue and + describe the use-case that demands support for empty + usernames. + +------------------------------------------------------------------- Old: ---- keyring-25.2.1.tar.gz New: ---- keyring-25.6.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-keyring.spec ++++++ --- /var/tmp/diff_new_pack.yubJHt/_old 2025-09-30 17:35:10.191054687 +0200 +++ /var/tmp/diff_new_pack.yubJHt/_new 2025-09-30 17:35:10.191054687 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-keyring # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -31,7 +31,7 @@ %endif %{?sle15_python_module_pythons} Name: python-keyring%{psuffix} -Version: 25.2.1 +Version: 25.6.0 Release: 0 Summary: System keyring service access from Python License: MIT @@ -65,6 +65,7 @@ %endif %if %{with test} BuildRequires: %{python_module keyring = %{version}} +BuildRequires: %{python_module pyfakefs} BuildRequires: %{python_module pytest >= 3.5} %endif %python_subpackages ++++++ keyring-25.2.1.tar.gz -> keyring-25.6.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/.coveragerc new/keyring-25.6.0/.coveragerc --- old/keyring-25.2.1/.coveragerc 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/.coveragerc 2024-12-25 16:26:26.000000000 +0100 @@ -8,6 +8,8 @@ [report] show_missing = True exclude_also = - # jaraco/skeleton#97 - @overload + # Exclude common false positives per + # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion + # Ref jaraco/skeleton#97 and jaraco/skeleton#135 + class .*\bProtocol\): if TYPE_CHECKING: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/.github/workflows/main.yml new/keyring-25.6.0/.github/workflows/main.yml --- old/keyring-25.2.1/.github/workflows/main.yml 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/.github/workflows/main.yml 2024-12-25 16:26:26.000000000 +0100 @@ -10,6 +10,7 @@ # required if branches-ignore is supplied (jaraco/skeleton#103) - '**' pull_request: + workflow_dispatch: permissions: contents: read @@ -34,23 +35,25 @@ # https://blog.jaraco.com/efficient-use-of-ci-resources/ matrix: python: - - "3.8" - - "3.12" + - "3.9" + - "3.13" platform: - ubuntu-latest - macos-latest - windows-latest include: - - python: "3.9" - platform: ubuntu-latest - python: "3.10" platform: ubuntu-latest - python: "3.11" platform: ubuntu-latest + - python: "3.12" + platform: ubuntu-latest + - python: "3.14" + platform: ubuntu-latest - python: pypy3.10 platform: ubuntu-latest runs-on: ${{ matrix.platform }} - continue-on-error: ${{ matrix.python == '3.13' }} + continue-on-error: ${{ matrix.python == '3.14' }} steps: - uses: actions/checkout@v4 - name: Setup Python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/.pre-commit-config.yaml new/keyring-25.6.0/.pre-commit-config.yaml --- old/keyring-25.2.1/.pre-commit-config.yaml 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/.pre-commit-config.yaml 2024-12-25 16:26:26.000000000 +0100 @@ -1,6 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.7.1 hooks: - id: ruff + args: [--fix, --unsafe-fixes] - id: ruff-format diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/.readthedocs.yaml new/keyring-25.6.0/.readthedocs.yaml --- old/keyring-25.2.1/.readthedocs.yaml 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/.readthedocs.yaml 2024-12-25 16:26:26.000000000 +0100 @@ -3,7 +3,7 @@ install: - path: . extra_requirements: - - docs + - doc # required boilerplate readthedocs/readthedocs.org#10401 build: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/NEWS.rst new/keyring-25.6.0/NEWS.rst --- old/keyring-25.2.1/NEWS.rst 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/NEWS.rst 2024-12-25 16:26:26.000000000 +0100 @@ -1,3 +1,54 @@ +v25.6.0 +======= + +Features +-------- + +- Avoid logging a warning when config does not specify a backend. (#682) + + +v25.5.0 +======= + +Features +-------- + +- When parsing ``keyring_path`` from the config, the home directory is now expanded from ``~``. (#696) + + +Bugfixes +-------- + +- In get_credential, now returns None when the indicated username is not found. (#698) + + +v25.4.1 +======= + +Bugfixes +-------- + +- Fixed ValueError for AnonymousCredentials in CLI. (#694) + + +v25.4.0 +======= + +Features +-------- + +- Refined type spec and interfaces on credential objects. Introduced AnonymousCredential to model a secret without a username. (#689) + + +v25.3.0 +======= + +Features +-------- + +- Deprecated support for empty usernames. Now all backends will reject an empty string as input for the 'username' field when setting a password. Later this deprecation will become a more visible user warning and even later an error. If this warning is triggered in your environment, please consider using a static value (even 'username') or comment in the issue and describe the use-case that demands support for empty usernames. (#668) + + v25.2.1 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/PKG-INFO new/keyring-25.6.0/PKG-INFO --- old/keyring-25.2.1/PKG-INFO 2024-05-13 21:02:21.069935600 +0200 +++ new/keyring-25.6.0/PKG-INFO 2024-12-25 16:26:41.642173300 +0100 @@ -1,16 +1,16 @@ Metadata-Version: 2.1 Name: keyring -Version: 25.2.1 +Version: 25.6.0 Summary: Store and access your passwords safely. Author-email: Kang Zhang <[email protected]> Maintainer-email: "Jason R. Coombs" <[email protected]> -Project-URL: Homepage, https://github.com/jaraco/keyring +Project-URL: Source, https://github.com/jaraco/keyring Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Requires-Python: >=3.8 +Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pywin32-ctypes>=0.2.0; sys_platform == "win32" @@ -21,20 +21,28 @@ Requires-Dist: importlib_resources; python_version < "3.9" Requires-Dist: jaraco.functools Requires-Dist: jaraco.context -Provides-Extra: testing -Requires-Dist: pytest!=8.1.*,>=6; extra == "testing" -Requires-Dist: pytest-checkdocs>=2.4; extra == "testing" -Requires-Dist: pytest-cov; extra == "testing" -Requires-Dist: pytest-mypy; extra == "testing" -Requires-Dist: pytest-enabler>=2.2; extra == "testing" -Requires-Dist: pytest-ruff>=0.2.1; extra == "testing" -Provides-Extra: docs -Requires-Dist: sphinx>=3.5; extra == "docs" -Requires-Dist: jaraco.packaging>=9.3; extra == "docs" -Requires-Dist: rst.linker>=1.9; extra == "docs" -Requires-Dist: furo; extra == "docs" -Requires-Dist: sphinx-lint; extra == "docs" -Requires-Dist: jaraco.tidelift>=1.4; extra == "docs" +Provides-Extra: test +Requires-Dist: pytest!=8.1.*,>=6; extra == "test" +Requires-Dist: pyfakefs; extra == "test" +Provides-Extra: doc +Requires-Dist: sphinx>=3.5; extra == "doc" +Requires-Dist: jaraco.packaging>=9.3; extra == "doc" +Requires-Dist: rst.linker>=1.9; extra == "doc" +Requires-Dist: furo; extra == "doc" +Requires-Dist: sphinx-lint; extra == "doc" +Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" +Provides-Extra: check +Requires-Dist: pytest-checkdocs>=2.4; extra == "check" +Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" +Provides-Extra: cover +Requires-Dist: pytest-cov; extra == "cover" +Provides-Extra: enabler +Requires-Dist: pytest-enabler>=2.2; extra == "enabler" +Provides-Extra: type +Requires-Dist: pytest-mypy; extra == "type" +Requires-Dist: pygobject-stubs; extra == "type" +Requires-Dist: shtab; extra == "type" +Requires-Dist: types-pywin32; extra == "type" Provides-Extra: completion Requires-Dist: shtab>=1.1.0; extra == "completion" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/docs/conf.py new/keyring-25.6.0/docs/conf.py --- old/keyring-25.2.1/docs/conf.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/docs/conf.py 2024-12-25 16:26:26.000000000 +0100 @@ -1,3 +1,5 @@ +from __future__ import annotations + extensions = [ 'sphinx.ext.autodoc', 'jaraco.packaging.sphinx', @@ -30,6 +32,7 @@ # Be strict about any broken references nitpicky = True +nitpick_ignore: list[tuple[str, str]] = [] # Include Python intersphinx mapping to prevent failures # jaraco/skeleton#51 @@ -41,4 +44,15 @@ # Preserve authored syntax for defaults autodoc_preserve_defaults = True +# Add support for linking usernames, PyPI projects, Wikipedia pages +github_url = 'https://github.com/' +extlinks = { + 'user': (f'{github_url}%s', '@%s'), + 'pypi': ('https://pypi.org/project/%s', '%s'), + 'wiki': ('https://wikipedia.org/wiki/%s', '%s'), +} +extensions += ['sphinx.ext.extlinks'] + +# local + extensions += ['jaraco.tidelift'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/backend.py new/keyring-25.6.0/keyring/backend.py --- old/keyring-25.2.1/keyring/backend.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/backend.py 2024-12-25 16:26:26.000000000 +0100 @@ -6,10 +6,12 @@ import abc import copy +import functools import logging import operator import os import typing +import warnings from jaraco.context import ExceptionTrap from jaraco.functools import once @@ -27,18 +29,38 @@ class KeyringBackendMeta(abc.ABCMeta): """ - A metaclass that's both an ABCMeta and a type that keeps a registry of - all (non-abstract) types. + Specialized subclass behavior. + + Keeps a registry of all (non-abstract) types. + + Wraps set_password to validate the username. """ def __init__(cls, name, bases, dict): super().__init__(name, bases, dict) + cls._register() + cls._validate_username_in_set_password() + + def _register(cls): if not hasattr(cls, '_classes'): cls._classes = set() classes = cls._classes if not cls.__abstractmethods__: classes.add(cls) + def _validate_username_in_set_password(cls): + """ + Wrap ``set_password`` such to validate the passed username. + """ + orig = cls.set_password + + @functools.wraps(orig) + def wrapper(self, system, username, *args, **kwargs): + self._validate_username(username) + return orig(self, system, username, *args, **kwargs) + + cls.set_password = wrapper + class KeyringBackend(metaclass=KeyringBackendMeta): """The abstract base class of the keyring, every backend must implement @@ -92,7 +114,8 @@ """ parent, sep, mod_name = cls.__module__.rpartition('.') mod_name = mod_name.replace('_', ' ') - return ' '.join([mod_name, cls.__name__]) # type: ignore + # mypy doesn't see `cls` is `type[Self]`, might be fixable in jaraco.classes + return ' '.join([mod_name, cls.__name__]) # type: ignore[attr-defined] def __str__(self) -> str: keyring_class = type(self) @@ -103,6 +126,18 @@ """Get password of the username for the service""" return None + def _validate_username(self, username: str) -> None: + """ + Ensure the username is not empty. + """ + if not username: + warnings.warn( + "Empty usernames are deprecated. See #668", + DeprecationWarning, + stacklevel=3, + ) + # raise ValueError("Username cannot be empty") + @abc.abstractmethod def set_password(self, service: str, username: str, password: str) -> None: """Set password for the username of the service. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/backends/Windows.py new/keyring-25.6.0/keyring/backends/Windows.py --- old/keyring-25.2.1/keyring/backends/Windows.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/backends/Windows.py 2024-12-25 16:26:26.000000000 +0100 @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from jaraco.context import ExceptionTrap @@ -94,16 +96,20 @@ return f'{username}@{service}' def get_password(self, service, username): + res = self._resolve_credential(service, username) + return res and res.value + + def _resolve_credential( + self, service: str, username: str | None + ) -> DecodingCredential | None: # first attempt to get the password under the service name - res = self._get_password(service) - if not res or res['UserName'] != username: + res = self._read_credential(service) + if not res or username and res['UserName'] != username: # It wasn't found so attempt to get it with the compound name - res = self._get_password(self._compound_name(username, service)) - if not res: - return None - return res.value + res = self._read_credential(self._compound_name(username, service)) + return res - def _get_password(self, target): + def _read_credential(self, target): try: res = win32cred.CredRead( Type=win32cred.CRED_TYPE_GENERIC, TargetName=target @@ -115,7 +121,7 @@ return DecodingCredential(res) def set_password(self, service, username, password): - existing_pw = self._get_password(service) + existing_pw = self._read_credential(service) if existing_pw: # resave the existing password using a compound target existing_username = existing_pw['UserName'] @@ -142,7 +148,7 @@ compound = self._compound_name(username, service) deleted = False for target in service, compound: - existing_pw = self._get_password(target) + existing_pw = self._read_credential(target) if existing_pw and existing_pw['UserName'] == username: deleted = True self._delete_password(target) @@ -158,13 +164,5 @@ raise def get_credential(self, service, username): - res = None - # get the credentials associated with the provided username - if username: - res = self._get_password(self._compound_name(username, service)) - # get any first password under the service name - if not res: - res = self._get_password(service) - if not res: - return None - return SimpleCredential(res['UserName'], res.value) + res = self._resolve_credential(service, username) + return res and SimpleCredential(res['UserName'], res.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/backends/fail.py new/keyring-25.6.0/keyring/backends/fail.py --- old/keyring-25.2.1/keyring/backends/fail.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/backends/fail.py 2024-12-25 16:26:26.000000000 +0100 @@ -27,4 +27,4 @@ ) raise NoKeyringError(msg) - set_password = delete_password = get_password # type: ignore + set_password = delete_password = get_password diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/backends/libsecret.py new/keyring-25.6.0/keyring/backends/libsecret.py --- old/keyring-25.2.1/keyring/backends/libsecret.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/backends/libsecret.py 2024-12-25 16:26:26.000000000 +0100 @@ -42,7 +42,7 @@ ), ) - @properties.NonDataProperty # type: ignore + @properties.NonDataProperty def collection(self): return Secret.COLLECTION_DEFAULT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/backends/null.py new/keyring-25.6.0/keyring/backends/null.py --- old/keyring-25.2.1/keyring/backends/null.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/backends/null.py 2024-12-25 16:26:26.000000000 +0100 @@ -17,4 +17,4 @@ def get_password(self, service, username, password=None): pass - set_password = delete_password = get_password # type: ignore + set_password = delete_password = get_password diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/cli.py new/keyring-25.6.0/keyring/cli.py --- old/keyring-25.2.1/keyring/cli.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/cli.py 2024-12-25 16:26:26.000000000 +0100 @@ -13,15 +13,24 @@ core, credentials, delete_password, + get_credential, get_password, set_keyring, set_password, - get_credential, ) from .util import platform_ class CommandLineTool: + # Attributes set dynamically by the ArgumentParser + keyring_path: str | None + keyring_backend: str | None + get_mode: str + output_format: str + operation: str + service: str + username: str + def __init__(self): self.parser = argparse.ArgumentParser() self.parser.add_argument( @@ -123,24 +132,19 @@ getattr(self, f'_emit_{self.output_format}')(credential) def _emit_json(self, credential: credentials.Credential): - print( - json.dumps(dict(username=credential.username, password=credential.password)) - ) + print(json.dumps(credential._vars())) def _emit_plain(self, credential: credentials.Credential): - if credential.username: - print(credential.username) - print(credential.password) + for val in credential._vars().values(): + print(val) def _get_creds(self) -> credentials.Credential | None: - return get_credential(self.service, self.username) # type: ignore + return get_credential(self.service, self.username) def _get_password(self) -> credentials.Credential | None: - password = get_password(self.service, self.username) # type: ignore + password = get_password(self.service, self.username) return ( - credentials.SimpleCredential(None, password) - if password is not None - else None + credentials.AnonymousCredential(password) if password is not None else None ) def do_set(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/compat/py312.py new/keyring-25.6.0/keyring/compat/py312.py --- old/keyring-25.2.1/keyring/compat/py312.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/compat/py312.py 2024-12-25 16:26:26.000000000 +0100 @@ -3,7 +3,7 @@ __all__ = ['metadata'] -if sys.version_info > (3, 12): +if sys.version_info >= (3, 12): import importlib.metadata as metadata else: - import importlib_metadata as metadata # type: ignore + import importlib_metadata as metadata diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/core.py new/keyring-25.6.0/keyring/core.py --- old/keyring-25.2.1/keyring/core.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/core.py 2024-12-25 16:26:26.000000000 +0100 @@ -37,6 +37,13 @@ def disable() -> None: """ Configure the null keyring as the default. + + >>> fs = getfixture('fs') + >>> disable() + >>> disable() + Traceback (most recent call last): + ... + RuntimeError: Refusing to overwrite... """ root = platform.config_root() try: @@ -47,7 +54,7 @@ if os.path.exists(filename): msg = f"Refusing to overwrite {filename}" raise RuntimeError(msg) - with open(filename, 'w') as file: + with open(filename, 'w', encoding='utf-8') as file: file.write('[backend]\ndefault-keyring=keyring.backends.null.Keyring') @@ -99,7 +106,7 @@ or load_config() or max( # all keyrings passing the limit filter - filter(limit, backend.get_all_keyring()), # type: ignore # 659 + filter(limit, backend.get_all_keyring()), # type: ignore[arg-type] #659 default=fail.Keyring(), key=backend.by_priority, ) @@ -171,7 +178,7 @@ if config.has_section("backend"): keyring_name = config.get("backend", "default-keyring").strip() else: - raise configparser.NoOptionError('backend', 'default-keyring') + return None except (configparser.NoOptionError, ImportError): logger = logging.getLogger('keyring') @@ -188,6 +195,6 @@ "load the keyring-path option (if present)" try: path = config.get("backend", "keyring-path").strip() - sys.path.insert(0, path) + sys.path.insert(0, os.path.expanduser(path)) except (configparser.NoOptionError, configparser.NoSectionError): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/credentials.py new/keyring-25.6.0/keyring/credentials.py --- old/keyring-25.2.1/keyring/credentials.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/credentials.py 2024-12-25 16:26:26.000000000 +0100 @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import os @@ -6,30 +8,43 @@ """Abstract class to manage credentials""" @abc.abstractproperty - def username(self): - return None + def username(self) -> str: ... @abc.abstractproperty - def password(self): - return None + def password(self) -> str: ... + + def _vars(self) -> dict[str, str]: + return dict(username=self.username, password=self.password) class SimpleCredential(Credential): """Simple credentials implementation""" - def __init__(self, username, password): + def __init__(self, username: str, password: str): self._username = username self._password = password @property - def username(self): + def username(self) -> str: return self._username @property - def password(self): + def password(self) -> str: return self._password +class AnonymousCredential(SimpleCredential): + def __init__(self, password: str): + self._password = password + + @property + def username(self) -> str: + raise ValueError("Anonymous credential has no username") + + def _vars(self) -> dict[str, str]: + return dict(password=self.password) + + class EnvironCredential(Credential): """ Source credentials from environment variables. @@ -47,14 +62,14 @@ False """ - def __init__(self, user_env_var, pwd_env_var): + def __init__(self, user_env_var: str, pwd_env_var: str): self.user_env_var = user_env_var self.pwd_env_var = pwd_env_var def __eq__(self, other: object) -> bool: return vars(self) == vars(other) - def _get_env(self, env_var): + def _get_env(self, env_var: str) -> str: """Helper to read an environment variable""" value = os.environ.get(env_var) if not value: @@ -62,9 +77,9 @@ return value @property - def username(self): + def username(self) -> str: return self._get_env(self.user_env_var) @property - def password(self): + def password(self) -> str: return self._get_env(self.pwd_env_var) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring/testing/backend.py new/keyring-25.6.0/keyring/testing/backend.py --- old/keyring-25.2.1/keyring/testing/backend.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/keyring/testing/backend.py 2024-12-25 16:26:26.000000000 +0100 @@ -163,6 +163,12 @@ ('user2', 'password2'), ) + @pytest.mark.xfail("platform.system() == 'Windows'", reason="#668") + def test_empty_username(self): + with pytest.deprecated_call(): + self.set_password('service1', '', 'password1') + assert self.keyring.get_password('service1', '') == 'password1' + def test_set_properties(self, monkeypatch): env = dict(KEYRING_PROPERTY_FOO_BAR='fizz buzz', OTHER_SETTING='ignore me') monkeypatch.setattr(os, 'environ', env) @@ -175,3 +181,20 @@ assert alt.foo == 'bar' with pytest.raises(AttributeError): self.keyring.foo # noqa: B018 + + def test_wrong_username_returns_none(self): + keyring = self.keyring + service = 'test_wrong_username_returns_none' + cred = keyring.get_credential(service, None) + assert cred is None + + password_1 = 'password1' + password_2 = 'password2' + self.set_password(service, 'user1', password_1) + self.set_password(service, 'user2', password_2) + + assert keyring.get_credential(service, "user1").password == password_1 + assert keyring.get_credential(service, "user2").password == password_2 + + # Missing/wrong username should not return a cred + assert keyring.get_credential(service, "nobody!") is None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring.egg-info/PKG-INFO new/keyring-25.6.0/keyring.egg-info/PKG-INFO --- old/keyring-25.2.1/keyring.egg-info/PKG-INFO 2024-05-13 21:02:21.000000000 +0200 +++ new/keyring-25.6.0/keyring.egg-info/PKG-INFO 2024-12-25 16:26:41.000000000 +0100 @@ -1,16 +1,16 @@ Metadata-Version: 2.1 Name: keyring -Version: 25.2.1 +Version: 25.6.0 Summary: Store and access your passwords safely. Author-email: Kang Zhang <[email protected]> Maintainer-email: "Jason R. Coombs" <[email protected]> -Project-URL: Homepage, https://github.com/jaraco/keyring +Project-URL: Source, https://github.com/jaraco/keyring Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Requires-Python: >=3.8 +Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pywin32-ctypes>=0.2.0; sys_platform == "win32" @@ -21,20 +21,28 @@ Requires-Dist: importlib_resources; python_version < "3.9" Requires-Dist: jaraco.functools Requires-Dist: jaraco.context -Provides-Extra: testing -Requires-Dist: pytest!=8.1.*,>=6; extra == "testing" -Requires-Dist: pytest-checkdocs>=2.4; extra == "testing" -Requires-Dist: pytest-cov; extra == "testing" -Requires-Dist: pytest-mypy; extra == "testing" -Requires-Dist: pytest-enabler>=2.2; extra == "testing" -Requires-Dist: pytest-ruff>=0.2.1; extra == "testing" -Provides-Extra: docs -Requires-Dist: sphinx>=3.5; extra == "docs" -Requires-Dist: jaraco.packaging>=9.3; extra == "docs" -Requires-Dist: rst.linker>=1.9; extra == "docs" -Requires-Dist: furo; extra == "docs" -Requires-Dist: sphinx-lint; extra == "docs" -Requires-Dist: jaraco.tidelift>=1.4; extra == "docs" +Provides-Extra: test +Requires-Dist: pytest!=8.1.*,>=6; extra == "test" +Requires-Dist: pyfakefs; extra == "test" +Provides-Extra: doc +Requires-Dist: sphinx>=3.5; extra == "doc" +Requires-Dist: jaraco.packaging>=9.3; extra == "doc" +Requires-Dist: rst.linker>=1.9; extra == "doc" +Requires-Dist: furo; extra == "doc" +Requires-Dist: sphinx-lint; extra == "doc" +Requires-Dist: jaraco.tidelift>=1.4; extra == "doc" +Provides-Extra: check +Requires-Dist: pytest-checkdocs>=2.4; extra == "check" +Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" +Provides-Extra: cover +Requires-Dist: pytest-cov; extra == "cover" +Provides-Extra: enabler +Requires-Dist: pytest-enabler>=2.2; extra == "enabler" +Provides-Extra: type +Requires-Dist: pytest-mypy; extra == "type" +Requires-Dist: pygobject-stubs; extra == "type" +Requires-Dist: shtab; extra == "type" +Requires-Dist: types-pywin32; extra == "type" Provides-Extra: completion Requires-Dist: shtab>=1.1.0; extra == "completion" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/keyring.egg-info/requires.txt new/keyring-25.6.0/keyring.egg-info/requires.txt --- old/keyring-25.2.1/keyring.egg-info/requires.txt 2024-05-13 21:02:21.000000000 +0200 +++ new/keyring-25.6.0/keyring.egg-info/requires.txt 2024-12-25 16:26:41.000000000 +0100 @@ -15,10 +15,19 @@ [:sys_platform == "win32"] pywin32-ctypes>=0.2.0 +[check] +pytest-checkdocs>=2.4 + +[check:sys_platform != "cygwin"] +pytest-ruff>=0.2.1 + [completion] shtab>=1.1.0 -[docs] +[cover] +pytest-cov + +[doc] sphinx>=3.5 jaraco.packaging>=9.3 rst.linker>=1.9 @@ -26,10 +35,15 @@ sphinx-lint jaraco.tidelift>=1.4 -[testing] +[enabler] +pytest-enabler>=2.2 + +[test] pytest!=8.1.*,>=6 -pytest-checkdocs>=2.4 -pytest-cov +pyfakefs + +[type] pytest-mypy -pytest-enabler>=2.2 -pytest-ruff>=0.2.1 +pygobject-stubs +shtab +types-pywin32 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/mypy.ini new/keyring-25.6.0/mypy.ini --- old/keyring-25.2.1/mypy.ini 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/mypy.ini 2024-12-25 16:26:26.000000000 +0100 @@ -1,5 +1,19 @@ [mypy] -ignore_missing_imports = True -# required to support namespace packages -# https://github.com/python/mypy/issues/14057 +# Is the project well-typed? +strict = False + +# Early opt-in even when strict = False +warn_unused_ignores = True +warn_redundant_casts = True +enable_error_code = ignore-without-code + +# Support namespace packages per https://github.com/python/mypy/issues/14057 explicit_package_bases = True + +disable_error_code = + # Disable due to many false positives + overload-overlap, + +# TODO: Open upstream issues requesting typing +[mypy-win32ctypes.*,secretstorage.*,dbus.*] +ignore_missing_imports = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/pyproject.toml new/keyring-25.6.0/pyproject.toml --- old/keyring-25.2.1/pyproject.toml 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/pyproject.toml 2024-12-25 16:26:26.000000000 +0100 @@ -19,7 +19,7 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ 'pywin32-ctypes>=0.2.0; sys_platform=="win32"', 'SecretStorage>=3.2; sys_platform=="linux"', @@ -33,21 +33,18 @@ dynamic = ["version"] [project.urls] -Homepage = "https://github.com/jaraco/keyring" +Source = "https://github.com/jaraco/keyring" [project.optional-dependencies] -testing = [ +test = [ # upstream "pytest >= 6, != 8.1.*", - "pytest-checkdocs >= 2.4", - "pytest-cov", - "pytest-mypy", - "pytest-enabler >= 2.2", - "pytest-ruff >= 0.2.1", # local + "pyfakefs", ] -docs = [ + +doc = [ # upstream "sphinx >= 3.5", "jaraco.packaging >= 9.3", @@ -60,6 +57,30 @@ # local ] + +check = [ + "pytest-checkdocs >= 2.4", + "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", +] + +cover = [ + "pytest-cov", +] + +enabler = [ + "pytest-enabler >= 2.2", +] + +type = [ + # upstream + "pytest-mypy", + + # local + "pygobject-stubs", + "shtab", # Optional install for completion + "types-pywin32", +] + completion = ["shtab >= 1.1.0"] [project.entry-points] @@ -76,4 +97,5 @@ [project.scripts] keyring = "keyring.cli:main" + [tool.setuptools_scm] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/ruff.toml new/keyring-25.6.0/ruff.toml --- old/keyring-25.2.1/ruff.toml 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/ruff.toml 2024-12-25 16:26:26.000000000 +0100 @@ -1,6 +1,10 @@ +# extend pyproject.toml for requires-python (workaround astral-sh/ruff#10299) +extend = "pyproject.toml" + [lint] extend-select = [ "C901", + "PERF401", "W", ] ignore = [ @@ -22,7 +26,8 @@ ] [format] -# Enable preview, required for quote-style = "preserve" +# Enable preview to get hugged parenthesis unwrapping and other nice surprises +# See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 preview = true -# https://docs.astral.sh/ruff/settings/#format-quote-style +# https://docs.astral.sh/ruff/settings/#format_quote-style quote-style = "preserve" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/tests/test_cli.py new/keyring-25.6.0/tests/test_cli.py --- old/keyring-25.2.1/tests/test_cli.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/tests/test_cli.py 2024-12-25 16:26:26.000000000 +0100 @@ -6,6 +6,7 @@ import pytest from keyring import cli +from keyring import credentials flatten = itertools.chain.from_iterable @@ -36,6 +37,12 @@ yield set_password [email protected] +def mocked_get_credential(): + with mock.patch('keyring.cli.get_credential') as get_credential: + yield get_credential + + def test_set_interactive(monkeypatch, mocked_set): tool = cli.CommandLineTool() tool.service = 'svc' @@ -64,3 +71,27 @@ monkeypatch.setattr(sys.stdin, 'read', lambda: 'foo123\n') tool.do_set() mocked_set.assert_called_once_with('svc', 'usr', 'foo123') + + [email protected]('format', ['json', 'plain']) +def test_get_anonymous(monkeypatch, mocked_get_credential, format, capsys): + mocked_get_credential.return_value = credentials.AnonymousCredential('s3cret') + tool = cli.CommandLineTool() + tool.service = 'svc' + tool.username = None + tool.get_mode = 'creds' + tool.output_format = format + tool.do_get() + assert 's3cret' in capsys.readouterr().out + + [email protected]('format', ['json', 'plain']) +def test_get(monkeypatch, mocked_get_credential, format, capsys): + mocked_get_credential.return_value = credentials.SimpleCredential('alice', 's3cret') + tool = cli.CommandLineTool() + tool.service = 'svc' + tool.username = 'alice' + tool.get_mode = 'creds' + tool.output_format = format + tool.do_get() + assert 's3cret' in capsys.readouterr().out diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/tests/test_core.py new/keyring-25.6.0/tests/test_core.py --- old/keyring-25.2.1/tests/test_core.py 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/tests/test_core.py 2024-12-25 16:26:26.000000000 +0100 @@ -25,6 +25,12 @@ assert not caplog.records +def test_load_empty_config(caplog, config_path): + config_path.write_text("", encoding='utf-8') + assert keyring.core.load_config() is None + assert not caplog.records + + fail_config = textwrap.dedent( """ [backend] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring-25.2.1/tox.ini new/keyring-25.6.0/tox.ini --- old/keyring-25.2.1/tox.ini 2024-05-13 21:02:01.000000000 +0200 +++ new/keyring-25.6.0/tox.ini 2024-12-25 16:26:26.000000000 +0100 @@ -7,7 +7,11 @@ pytest {posargs} usedevelop = True extras = - testing + test + check + cover + enabler + type [testenv:diffcov] description = run tests and check that diff from main is covered @@ -22,14 +26,12 @@ [testenv:docs] description = build the documentation extras = - docs - testing + doc + test changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html - python -m sphinxlint \ - # workaround for sphinx-contrib/sphinx-lint#83 - --jobs 1 + python -m sphinxlint [testenv:finalize] description = assemble changelog and tag a release
