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

Reply via email to