Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-typeguard for 
openSUSE:Factory checked in at 2024-11-01 21:00:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-typeguard (Old)
 and      /work/SRC/openSUSE:Factory/.python-typeguard.new.2020 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-typeguard"

Fri Nov  1 21:00:42 2024 rev:7 rq:1219719 version:4.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-typeguard/python-typeguard.changes        
2024-06-10 17:37:01.841604707 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-typeguard.new.2020/python-typeguard.changes  
    2024-11-01 21:00:45.649378353 +0100
@@ -1,0 +2,11 @@
+Wed Oct 30 19:48:05 UTC 2024 - Dirk Müller <[email protected]>
+
+- update to 4.4.0:
+  * Added proper checking for method signatures in protocol
+    checks
+  * Fixed basic support for intersection protocols
+  * Fixed protocol checks running against the class of an
+    instance and not the instance itself (this produced wrong
+    results for non-method member checks)
+
+-------------------------------------------------------------------

Old:
----
  typeguard-4.3.0-gh.tar.gz

New:
----
  typeguard-4.4.0-gh.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-typeguard.spec ++++++
--- /var/tmp/diff_new_pack.wWLOKu/_old  2024-11-01 21:00:46.785425776 +0100
+++ /var/tmp/diff_new_pack.wWLOKu/_new  2024-11-01 21:00:46.797426276 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-typeguard
-Version:        4.3.0
+Version:        4.4.0
 Release:        0
 Summary:        Library for runtime checking of Python types
 License:        MIT

++++++ typeguard-4.3.0-gh.tar.gz -> typeguard-4.4.0-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/.pre-commit-config.yaml 
new/typeguard-4.4.0/.pre-commit-config.yaml
--- old/typeguard-4.3.0/.pre-commit-config.yaml 2024-05-27 11:44:38.000000000 
+0200
+++ new/typeguard-4.4.0/.pre-commit-config.yaml 2024-10-27 11:55:53.000000000 
+0100
@@ -14,14 +14,14 @@
       - id: trailing-whitespace
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.4.5
+    rev: v0.5.0
     hooks:
       - id: ruff
         args: [--fix, --show-fixes]
       - id: ruff-format
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.10.0
+    rev: v1.10.1
     hooks:
       - id: mypy
         additional_dependencies: [ "typing_extensions" ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/docs/features.rst 
new/typeguard-4.4.0/docs/features.rst
--- old/typeguard-4.3.0/docs/features.rst       2024-05-27 11:44:38.000000000 
+0200
+++ new/typeguard-4.4.0/docs/features.rst       2024-10-27 11:55:53.000000000 
+0100
@@ -62,15 +62,11 @@
 +++++++++++++++++
 
 As of version 4.3.0, Typeguard can check instances and classes against 
Protocols,
-regardless of whether they were annotated with 
:decorator:`typing.runtime_checkable`.
+regardless of whether they were annotated with
+:func:`@runtime_checkable <typing.runtime_checkable>`.
 
-There are several limitations on the checks performed, however:
-
-* For non-callable members, only presence is checked for; no type 
compatibility checks
-  are performed
-* For methods, only the number of positional arguments are checked against, so 
any added
-  keyword-only arguments without defaults don't currently trip the checker
-* Likewise, argument types are not checked for compatibility
+The only current limitation is that argument annotations are not checked for
+compatibility, however this should be covered by static type checkers pretty 
well.
 
 Special considerations for ``if TYPE_CHECKING:``
 ------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/docs/versionhistory.rst 
new/typeguard-4.4.0/docs/versionhistory.rst
--- old/typeguard-4.3.0/docs/versionhistory.rst 2024-05-27 11:44:38.000000000 
+0200
+++ new/typeguard-4.4.0/docs/versionhistory.rst 2024-10-27 11:55:53.000000000 
+0100
@@ -4,6 +4,15 @@
 This library adheres to
 `Semantic Versioning 2.0 <https://semver.org/#semantic-versioning-200>`_.
 
+**4.4.0** (2024-10-27)
+
+- Added proper checking for method signatures in protocol checks
+  (`#465 <https://github.com/agronholm/typeguard/pull/465>`_)
+- Fixed basic support for intersection protocols
+  (`#490 <https://github.com/agronholm/typeguard/pull/490>`_; PR by 
@antonagestam)
+- Fixed protocol checks running against the class of an instance and not the 
instance
+  itself (this produced wrong results for non-method member checks)
+
 **4.3.0** (2024-05-27)
 
 - Added support for checking against static protocols
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/pyproject.toml 
new/typeguard-4.4.0/pyproject.toml
--- old/typeguard-4.3.0/pyproject.toml  2024-05-27 11:44:38.000000000 +0200
+++ new/typeguard-4.4.0/pyproject.toml  2024-10-27 11:55:53.000000000 +0100
@@ -22,6 +22,7 @@
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
 ]
 requires-python = ">= 3.8"
 dependencies = [
@@ -80,11 +81,11 @@
 
 [tool.ruff.lint]
 extend-select = [
-    "W",            # pycodestyle warnings
+    "B0",           # flake8-bugbear
     "I",            # isort
     "PGH",          # pygrep-hooks
     "UP",           # pyupgrade
-    "B0",           # flake8-bugbear
+    "W",            # pycodestyle warnings
 ]
 ignore = [
     "S307",
@@ -97,19 +98,15 @@
 pretty = true
 
 [tool.tox]
-legacy_tox_ini = """
-[tox]
-envlist = pypy3, py38, py39, py310, py311, py312, py313
+env_list = ["py38", "py39", "py310", "py311", "py312", "py313"]
 skip_missing_interpreters = true
-minversion = 4.0
 
-[testenv]
-extras = test
-commands = coverage run -m pytest {posargs}
-package = editable
-
-[testenv:docs]
-extras = doc
-package = editable
-commands = sphinx-build -W -n docs build/sphinx
-"""
+[tool.tox.env_run_base]
+commands = [["coverage", "run", "-m", "pytest", { replace = "posargs", extend 
= true }]]
+package = "editable"
+extras = ["test"]
+
+[tool.tox.env.docs]
+depends = []
+extras = ["doc"]
+commands = [["sphinx-build", "-W", "-n", "docs", "build/sphinx"]]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/src/typeguard/_checkers.py 
new/typeguard-4.4.0/src/typeguard/_checkers.py
--- old/typeguard-4.3.0/src/typeguard/_checkers.py      2024-05-27 
11:44:38.000000000 +0200
+++ new/typeguard-4.4.0/src/typeguard/_checkers.py      2024-10-27 
11:55:53.000000000 +0100
@@ -9,6 +9,7 @@
 from enum import Enum
 from inspect import Parameter, isclass, isfunction
 from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
+from itertools import zip_longest
 from textwrap import indent
 from typing import (
     IO,
@@ -32,12 +33,8 @@
     Union,
 )
 from unittest.mock import Mock
-from weakref import WeakKeyDictionary
 
-try:
-    import typing_extensions
-except ImportError:
-    typing_extensions = None  # type: ignore[assignment]
+import typing_extensions
 
 # Must use this because typing.is_typeddict does not recognize
 # TypedDict from typing_extensions, and as of version 4.12.0
@@ -89,10 +86,6 @@
 if sys.version_info >= (3, 9):
     generic_alias_types += (types.GenericAlias,)
 
-protocol_check_cache: WeakKeyDictionary[
-    type[Any], dict[type[Any], TypeCheckError | None]
-] = WeakKeyDictionary()
-
 # Sentinel
 _missing = object()
 
@@ -548,15 +541,8 @@
             )
 
 
-if typing_extensions is None:
-
-    def _is_literal_type(typ: object) -> bool:
-        return typ is typing.Literal
-
-else:
-
-    def _is_literal_type(typ: object) -> bool:
-        return typ is typing.Literal or typ is typing_extensions.Literal
+def _is_literal_type(typ: object) -> bool:
+    return typ is typing.Literal or typ is typing_extensions.Literal
 
 
 def check_literal(
@@ -648,102 +634,196 @@
         raise TypeCheckError("is not an I/O object")
 
 
-def check_protocol(
-    value: Any,
-    origin_type: Any,
-    args: tuple[Any, ...],
-    memo: TypeCheckMemo,
+def check_signature_compatible(
+    subject_callable: Callable[..., Any], protocol: type, attrname: str
 ) -> None:
-    subject: type[Any] = value if isclass(value) else type(value)
+    subject_sig = inspect.signature(subject_callable)
+    protocol_sig = inspect.signature(getattr(protocol, attrname))
+    protocol_type: typing.Literal["instance", "class", "static"] = "instance"
+    subject_type: typing.Literal["instance", "class", "static"] = "instance"
+
+    # Check if the protocol-side method is a class method or static method
+    if attrname in protocol.__dict__:
+        descriptor = protocol.__dict__[attrname]
+        if isinstance(descriptor, staticmethod):
+            protocol_type = "static"
+        elif isinstance(descriptor, classmethod):
+            protocol_type = "class"
+
+    # Check if the subject-side method is a class method or static method
+    if inspect.ismethod(subject_callable) and inspect.isclass(
+        subject_callable.__self__
+    ):
+        subject_type = "class"
+    elif not hasattr(subject_callable, "__self__"):
+        subject_type = "static"
 
-    if subject in protocol_check_cache:
-        result_map = protocol_check_cache[subject]
-        if origin_type in result_map:
-            if exc := result_map[origin_type]:
-                raise exc
-            else:
-                return
+    if protocol_type == "instance" and subject_type != "instance":
+        raise TypeCheckError(
+            f"should be an instance method but it's a {subject_type} method"
+        )
+    elif protocol_type != "instance" and subject_type == "instance":
+        raise TypeCheckError(
+            f"should be a {protocol_type} method but it's an instance method"
+        )
+
+    expected_varargs = any(
+        param
+        for param in protocol_sig.parameters.values()
+        if param.kind is Parameter.VAR_POSITIONAL
+    )
+    has_varargs = any(
+        param
+        for param in subject_sig.parameters.values()
+        if param.kind is Parameter.VAR_POSITIONAL
+    )
+    if expected_varargs and not has_varargs:
+        raise TypeCheckError("should accept variable positional arguments but 
doesn't")
+
+    protocol_has_varkwargs = any(
+        param
+        for param in protocol_sig.parameters.values()
+        if param.kind is Parameter.VAR_KEYWORD
+    )
+    subject_has_varkwargs = any(
+        param
+        for param in subject_sig.parameters.values()
+        if param.kind is Parameter.VAR_KEYWORD
+    )
+    if protocol_has_varkwargs and not subject_has_varkwargs:
+        raise TypeCheckError("should accept variable keyword arguments but 
doesn't")
+
+    # Check that the callable has at least the expect amount of positional-only
+    # arguments (and no extra positional-only arguments without default values)
+    if not has_varargs:
+        protocol_args = [
+            param
+            for param in protocol_sig.parameters.values()
+            if param.kind
+            in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
+        ]
+        subject_args = [
+            param
+            for param in subject_sig.parameters.values()
+            if param.kind
+            in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
+        ]
+
+        # Remove the "self" parameter from the protocol arguments to match
+        if protocol_type == "instance":
+            protocol_args.pop(0)
+
+        for protocol_arg, subject_arg in zip_longest(protocol_args, 
subject_args):
+            if protocol_arg is None:
+                if subject_arg.default is Parameter.empty:
+                    raise TypeCheckError("has too many mandatory positional 
arguments")
+
+                break
+
+            if subject_arg is None:
+                raise TypeCheckError("has too few positional arguments")
+
+            if (
+                protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
+                and subject_arg.kind is Parameter.POSITIONAL_ONLY
+            ):
+                raise TypeCheckError(
+                    f"has an argument ({subject_arg.name}) that should not be "
+                    f"positional-only"
+                )
 
-    # Collect a set of methods and non-method attributes present in the 
protocol
-    ignored_attrs = set(dir(typing.Protocol)) | {
-        "__annotations__",
-        "__non_callable_proto_members__",
+            if (
+                protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
+                and protocol_arg.name != subject_arg.name
+            ):
+                raise TypeCheckError(
+                    f"has a positional argument ({subject_arg.name}) that 
should be "
+                    f"named {protocol_arg.name!r} at this position"
+                )
+
+    protocol_kwonlyargs = {
+        param.name: param
+        for param in protocol_sig.parameters.values()
+        if param.kind is Parameter.KEYWORD_ONLY
+    }
+    subject_kwonlyargs = {
+        param.name: param
+        for param in subject_sig.parameters.values()
+        if param.kind is Parameter.KEYWORD_ONLY
     }
-    expected_methods: dict[str, tuple[Any, Any]] = {}
-    expected_noncallable_members: dict[str, Any] = {}
-    for attrname in dir(origin_type):
-        # Skip attributes present in typing.Protocol
-        if attrname in ignored_attrs:
-            continue
-
-        member = getattr(origin_type, attrname)
-        if callable(member):
-            signature = inspect.signature(member)
-            argtypes = [
-                (p.annotation if p.annotation is not Parameter.empty else Any)
-                for p in signature.parameters.values()
-                if p.kind is not Parameter.KEYWORD_ONLY
-            ] or Ellipsis
-            return_annotation = (
-                signature.return_annotation
-                if signature.return_annotation is not Parameter.empty
-                else Any
+    if not subject_has_varkwargs:
+        # Check that the signature has at least the required keyword-only 
arguments, and
+        # no extra mandatory keyword-only arguments
+        if missing_kwonlyargs := [
+            param.name
+            for param in protocol_kwonlyargs.values()
+            if param.name not in subject_kwonlyargs
+        ]:
+            raise TypeCheckError(
+                "is missing keyword-only arguments: " + ", 
".join(missing_kwonlyargs)
             )
-            expected_methods[attrname] = argtypes, return_annotation
-        else:
-            expected_noncallable_members[attrname] = member
 
-    for attrname, annotation in typing.get_type_hints(origin_type).items():
-        expected_noncallable_members[attrname] = annotation
+    if not protocol_has_varkwargs:
+        if extra_kwonlyargs := [
+            param.name
+            for param in subject_kwonlyargs.values()
+            if param.default is Parameter.empty
+            and param.name not in protocol_kwonlyargs
+        ]:
+            raise TypeCheckError(
+                "has mandatory keyword-only arguments not present in the 
protocol: "
+                + ", ".join(extra_kwonlyargs)
+            )
 
-    subject_annotations = typing.get_type_hints(subject)
 
-    # Check that all required methods are present and their signatures are 
compatible
-    result_map = protocol_check_cache.setdefault(subject, {})
-    try:
-        for attrname, callable_args in expected_methods.items():
+def check_protocol(
+    value: Any,
+    origin_type: Any,
+    args: tuple[Any, ...],
+    memo: TypeCheckMemo,
+) -> None:
+    origin_annotations = typing.get_type_hints(origin_type)
+    for attrname in 
sorted(typing_extensions.get_protocol_members(origin_type)):
+        if (annotation := origin_annotations.get(attrname)) is not None:
             try:
-                method = getattr(subject, attrname)
+                subject_member = getattr(value, attrname)
             except AttributeError:
-                if attrname in subject_annotations:
-                    raise TypeCheckError(
-                        f"is not compatible with the 
{origin_type.__qualname__} protocol "
-                        f"because its {attrname!r} attribute is not a method"
-                    ) from None
-                else:
-                    raise TypeCheckError(
-                        f"is not compatible with the 
{origin_type.__qualname__} protocol "
-                        f"because it has no method named {attrname!r}"
-                    ) from None
-
-            if not callable(method):
                 raise TypeCheckError(
-                    f"is not compatible with the {origin_type.__qualname__} 
protocol "
-                    f"because its {attrname!r} attribute is not a callable"
-                )
+                    f"is not compatible with the {origin_type.__qualname__} "
+                    f"protocol because it has no attribute named {attrname!r}"
+                ) from None
 
-            # TODO: raise exception on added keyword-only arguments without 
defaults
             try:
-                check_callable(method, Callable, callable_args, memo)
+                check_type_internal(subject_member, annotation, memo)
             except TypeCheckError as exc:
                 raise TypeCheckError(
-                    f"is not compatible with the {origin_type.__qualname__} 
protocol "
-                    f"because its {attrname!r} method {exc}"
+                    f"is not compatible with the {origin_type.__qualname__} "
+                    f"protocol because its {attrname!r} attribute {exc}"
+                ) from None
+        elif callable(getattr(origin_type, attrname)):
+            try:
+                subject_member = getattr(value, attrname)
+            except AttributeError:
+                raise TypeCheckError(
+                    f"is not compatible with the {origin_type.__qualname__} "
+                    f"protocol because it has no method named {attrname!r}"
                 ) from None
 
-        # Check that all required non-callable members are present
-        for attrname in expected_noncallable_members:
-            # TODO: implement assignability checks for non-callable members
-            if attrname not in subject_annotations and not hasattr(subject, 
attrname):
+            if not callable(subject_member):
                 raise TypeCheckError(
-                    f"is not compatible with the {origin_type.__qualname__} 
protocol "
-                    f"because it has no attribute named {attrname!r}"
+                    f"is not compatible with the {origin_type.__qualname__} "
+                    f"protocol because its {attrname!r} attribute is not a 
callable"
                 )
-    except TypeCheckError as exc:
-        result_map[origin_type] = exc
-        raise
-    else:
-        result_map[origin_type] = None
+
+            # TODO: implement assignability checks for parameter and return 
value
+            #  annotations
+            try:
+                check_signature_compatible(subject_member, origin_type, 
attrname)
+            except TypeCheckError as exc:
+                raise TypeCheckError(
+                    f"is not compatible with the {origin_type.__qualname__} "
+                    f"protocol because its {attrname!r} method {exc}"
+                ) from None
 
 
 def check_byteslike(
@@ -905,6 +985,13 @@
     type: check_class,
     Type: check_class,
     Union: check_union,
+    # On some versions of Python, these may simply be re-exports from "typing",
+    # but exactly which Python versions is subject to change.
+    # It's best to err on the safe side and just always specify these.
+    typing_extensions.Literal: check_literal,
+    typing_extensions.LiteralString: check_literal_string,
+    typing_extensions.Self: check_self,
+    typing_extensions.TypeGuard: check_typeguard,
 }
 if sys.version_info >= (3, 10):
     origin_type_checkers[types.UnionType] = check_uniontype
@@ -913,16 +1000,6 @@
     origin_type_checkers.update(
         {typing.LiteralString: check_literal_string, typing.Self: check_self}
     )
-if typing_extensions is not None:
-    # On some Python versions, these may simply be re-exports from typing,
-    # but exactly which Python versions is subject to change,
-    # so it's best to err on the safe side
-    # and update the dictionary on all Python versions
-    # if typing_extensions is installed
-    origin_type_checkers[typing_extensions.Literal] = check_literal
-    origin_type_checkers[typing_extensions.LiteralString] = 
check_literal_string
-    origin_type_checkers[typing_extensions.Self] = check_self
-    origin_type_checkers[typing_extensions.TypeGuard] = check_typeguard
 
 
 def builtin_checker_lookup(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/src/typeguard/_decorators.py 
new/typeguard-4.4.0/src/typeguard/_decorators.py
--- old/typeguard-4.3.0/src/typeguard/_decorators.py    2024-05-27 
11:44:38.000000000 +0200
+++ new/typeguard-4.4.0/src/typeguard/_decorators.py    2024-10-27 
11:55:53.000000000 +0100
@@ -16,20 +16,18 @@
 from ._transformer import TypeguardTransformer
 from ._utils import Unset, function_name, get_stacklevel, is_method_of, unset
 
+T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
+
 if TYPE_CHECKING:
     from typeshed.stdlib.types import _Cell
 
-    _F = TypeVar("_F")
-
-    def typeguard_ignore(f: _F) -> _F:
+    def typeguard_ignore(f: T_CallableOrType) -> T_CallableOrType:
         """This decorator is a noop during static type-checking."""
         return f
 
 else:
     from typing import no_type_check as typeguard_ignore  # noqa: F401
 
-T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
-
 
 def make_cell(value: object) -> _Cell:
     return (lambda: value).__closure__[0]  # type: ignore[index]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/tests/__init__.py 
new/typeguard-4.4.0/tests/__init__.py
--- old/typeguard-4.3.0/tests/__init__.py       2024-05-27 11:44:38.000000000 
+0200
+++ new/typeguard-4.4.0/tests/__init__.py       2024-10-27 11:55:53.000000000 
+0100
@@ -6,10 +6,8 @@
     List,
     NamedTuple,
     NewType,
-    Protocol,
     TypeVar,
     Union,
-    runtime_checkable,
 )
 
 T_Foo = TypeVar("T_Foo")
@@ -44,16 +42,3 @@
 class Child(Parent):
     def method(self, a: int) -> None:
         pass
-
-
-class StaticProtocol(Protocol):
-    member: int
-
-    def meth(self, x: str) -> None: ...
-
-
-@runtime_checkable
-class RuntimeProtocol(Protocol):
-    member: int
-
-    def meth(self, x: str) -> None: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/tests/test_checkers.py 
new/typeguard-4.4.0/tests/test_checkers.py
--- old/typeguard-4.3.0/tests/test_checkers.py  2024-05-27 11:44:38.000000000 
+0200
+++ new/typeguard-4.4.0/tests/test_checkers.py  2024-10-27 11:55:53.000000000 
+0100
@@ -16,14 +16,17 @@
     Dict,
     ForwardRef,
     FrozenSet,
+    Iterable,
     Iterator,
     List,
     Literal,
     Mapping,
     MutableMapping,
     Optional,
+    Protocol,
     Sequence,
     Set,
+    Sized,
     TextIO,
     Tuple,
     Type,
@@ -51,8 +54,6 @@
     Employee,
     JSONType,
     Parent,
-    RuntimeProtocol,
-    StaticProtocol,
     TChild,
     TIntStr,
     TParent,
@@ -995,119 +996,318 @@
             check_type(f, TextIO)
 
 
[email protected](
-    "instantiate, annotation",
-    [
-        pytest.param(True, RuntimeProtocol, id="instance_runtime"),
-        pytest.param(False, Type[RuntimeProtocol], id="class_runtime"),
-        pytest.param(True, StaticProtocol, id="instance_static"),
-        pytest.param(False, Type[StaticProtocol], id="class_static"),
-    ],
-)
+class TestIntersectingProtocol:
+    SIT = TypeVar("SIT", covariant=True)
+
+    class SizedIterable(
+        Sized,
+        Iterable[SIT],
+        Protocol[SIT],
+    ): ...
+
+    @pytest.mark.parametrize(
+        "subject, predicate_type",
+        (
+            pytest.param(
+                (),
+                SizedIterable,
+                id="empty_tuple_unspecialized",
+            ),
+            pytest.param(
+                range(2),
+                SizedIterable,
+                id="range",
+            ),
+            pytest.param(
+                (),
+                SizedIterable[int],
+                id="empty_tuple_int_specialized",
+            ),
+            pytest.param(
+                (1, 2, 3),
+                SizedIterable[int],
+                id="tuple_int_specialized",
+            ),
+            pytest.param(
+                ("1", "2", "3"),
+                SizedIterable[str],
+                id="tuple_str_specialized",
+            ),
+        ),
+    )
+    def test_valid_member_passes(self, subject: object, predicate_type: type) 
-> None:
+        for _ in range(2):  # Makes sure that the cache is also exercised
+            check_type(subject, predicate_type)
+
+    xfail_nested_protocol_checks = pytest.mark.xfail(
+        reason="false negative due to missing support for nested protocol 
checks",
+    )
+
+    @pytest.mark.parametrize(
+        "subject, predicate_type",
+        (
+            pytest.param(
+                (1 for _ in ()),
+                SizedIterable,
+                id="generator",
+            ),
+            pytest.param(
+                range(2),
+                SizedIterable[str],
+                marks=xfail_nested_protocol_checks,
+                id="range_str_specialized",
+            ),
+            pytest.param(
+                (1, 2, 3),
+                SizedIterable[str],
+                marks=xfail_nested_protocol_checks,
+                id="int_tuple_str_specialized",
+            ),
+            pytest.param(
+                ("1", "2", "3"),
+                SizedIterable[int],
+                marks=xfail_nested_protocol_checks,
+                id="str_tuple_int_specialized",
+            ),
+        ),
+    )
+    def test_raises_for_non_member(self, subject: object, predicate_type: 
type) -> None:
+        with pytest.raises(TypeCheckError):
+            check_type(subject, predicate_type)
+
+
 class TestProtocol:
-    def test_member_defaultval(self, instantiate, annotation):
+    def test_success(self, typing_provider: Any) -> None:
+        class MyProtocol(Protocol):
+            member: int
+
+            def noargs(self) -> None:
+                pass
+
+            def posonlyargs(self, a: int, b: str, /) -> None:
+                pass
+
+            def posargs(self, a: int, b: str, c: float = 2.0) -> None:
+                pass
+
+            def varargs(self, *args: Any) -> None:
+                pass
+
+            def varkwargs(self, **kwargs: Any) -> None:
+                pass
+
+            def varbothargs(self, *args: Any, **kwargs: Any) -> None:
+                pass
+
+            @staticmethod
+            def my_static_method(x: int, y: str) -> None:
+                pass
+
+            @classmethod
+            def my_class_method(cls, x: int, y: str) -> None:
+                pass
+
         class Foo:
             member = 1
 
-            def meth(self, x: str) -> None:
+            def noargs(self, x: int = 1) -> None:
                 pass
 
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):  # Makes sure that the cache is also exercised
-            check_type(subject, annotation)
+            def posonlyargs(self, a: int, b: str, c: float = 2.0, /) -> None:
+                pass
 
-    def test_member_annotation(self, instantiate, annotation):
-        class Foo:
+            def posargs(self, *args: Any) -> None:
+                pass
+
+            def varargs(self, *args: Any, kwarg: str = "foo") -> None:
+                pass
+
+            def varkwargs(self, **kwargs: Any) -> None:
+                pass
+
+            def varbothargs(self, *args: Any, **kwargs: Any) -> None:
+                pass
+
+            # These were intentionally reversed, as this is OK for mypy
+            @classmethod
+            def my_static_method(cls, x: int, y: str) -> None:
+                pass
+
+            @staticmethod
+            def my_class_method(x: int, y: str) -> None:
+                pass
+
+        check_type(Foo(), MyProtocol)
+
+    @pytest.mark.parametrize("has_member", [True, False])
+    def test_member_checks(self, has_member: bool) -> None:
+        class MyProtocol(Protocol):
             member: int
 
+        class Foo:
+            def __init__(self, member: int):
+                if member:
+                    self.member = member
+
+        if has_member:
+            check_type(Foo(1), MyProtocol)
+        else:
+            pytest.raises(TypeCheckError, check_type, Foo(0), 
MyProtocol).match(
+                f"^{qualified_name(Foo)} is not compatible with the "
+                f"{MyProtocol.__qualname__} protocol because it has no 
attribute named "
+                f"'member'"
+            )
+
+    def test_missing_method(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self) -> None:
+                pass
+
+        class Foo:
+            pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because it has no method 
named "
+            f"'meth'"
+        )
+
+    def test_too_many_posargs(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self) -> None:
+                pass
+
+        class Foo:
             def meth(self, x: str) -> None:
                 pass
 
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            check_type(subject, annotation)
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method has 
too "
+            f"many mandatory positional arguments"
+        )
+
+    def test_wrong_posarg_name(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self, x: str) -> None:
+                pass
 
-    def test_attribute_missing(self, instantiate, annotation):
         class Foo:
-            val = 1
+            def meth(self, y: str) -> None:
+                pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            rf"^{qualified_name(Foo)} is not compatible with the "
+            rf"{MyProtocol.__qualname__} protocol because its 'meth' method 
has a "
+            rf"positional argument \(y\) that should be named 'x' at this 
position"
+        )
 
+    def test_too_few_posargs(self) -> None:
+        class MyProtocol(Protocol):
             def meth(self, x: str) -> None:
                 pass
 
-        clsname = 
f"{__name__}.TestProtocol.test_attribute_missing.<locals>.Foo"
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            pytest.raises(TypeCheckError, check_type, subject, 
annotation).match(
-                f"{clsname} is not compatible with the 
(Runtime|Static)Protocol "
-                f"protocol because it has no attribute named 'member'"
-            )
+        class Foo:
+            def meth(self) -> None:
+                pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method has 
too "
+            f"few positional arguments"
+        )
+
+    def test_no_varargs(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self, *args: Any) -> None:
+                pass
 
-    def test_method_missing(self, instantiate, annotation):
         class Foo:
-            member: int
+            def meth(self) -> None:
+                pass
 
-        pattern = (
-            f"{__name__}.TestProtocol.test_method_missing.<locals>.Foo is not "
-            f"compatible with the (Runtime|Static)Protocol protocol because it 
has no "
-            f"method named 'meth'"
-        )
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            pytest.raises(TypeCheckError, check_type, subject, 
annotation).match(
-                pattern
-            )
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method 
should "
+            f"accept variable positional arguments but doesn't"
+        )
+
+    def test_no_kwargs(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self, **kwargs: Any) -> None:
+                pass
 
-    def test_attribute_is_not_method_1(self, instantiate, annotation):
         class Foo:
-            member: int
-            meth: str
+            def meth(self) -> None:
+                pass
 
-        pattern = (
-            
f"{__name__}.TestProtocol.test_attribute_is_not_method_1.<locals>.Foo is "
-            f"not compatible with the (Runtime|Static)Protocol protocol 
because its "
-            f"'meth' attribute is not a method"
-        )
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            pytest.raises(TypeCheckError, check_type, subject, 
annotation).match(
-                pattern
-            )
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method 
should "
+            f"accept variable keyword arguments but doesn't"
+        )
+
+    def test_missing_kwarg(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self, *, x: str) -> None:
+                pass
 
-    def test_attribute_is_not_method_2(self, instantiate, annotation):
         class Foo:
-            member: int
-            meth = "foo"
+            def meth(self) -> None:
+                pass
 
-        pattern = (
-            
f"{__name__}.TestProtocol.test_attribute_is_not_method_2.<locals>.Foo is "
-            f"not compatible with the (Runtime|Static)Protocol protocol 
because its "
-            f"'meth' attribute is not a callable"
-        )
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            pytest.raises(TypeCheckError, check_type, subject, 
annotation).match(
-                pattern
-            )
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method is "
+            f"missing keyword-only arguments: x"
+        )
+
+    def test_extra_kwarg(self) -> None:
+        class MyProtocol(Protocol):
+            def meth(self) -> None:
+                pass
 
-    def test_method_signature_mismatch(self, instantiate, annotation):
         class Foo:
-            member: int
+            def meth(self, *, x: str) -> None:
+                pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method has 
"
+            f"mandatory keyword-only arguments not present in the protocol: x"
+        )
 
-            def meth(self, x: str, y: int) -> None:
+    def test_instance_staticmethod_mismatch(self) -> None:
+        class MyProtocol(Protocol):
+            @staticmethod
+            def meth() -> None:
                 pass
 
-        pattern = (
-            rf"(class 
)?{__name__}.TestProtocol.test_method_signature_mismatch."
-            rf"<locals>.Foo is not compatible with the 
(Runtime|Static)Protocol "
-            rf"protocol because its 'meth' method has too many mandatory 
positional "
-            rf"arguments in its declaration; expected 2 but 3 mandatory 
positional "
-            rf"argument\(s\) declared"
-        )
-        subject = Foo() if instantiate else Foo
-        for _ in range(2):
-            pytest.raises(TypeCheckError, check_type, subject, 
annotation).match(
-                pattern
-            )
+        class Foo:
+            def meth(self) -> None:
+                pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method 
should "
+            f"be a static method but it's an instance method"
+        )
+
+    def test_instance_classmethod_mismatch(self) -> None:
+        class MyProtocol(Protocol):
+            @classmethod
+            def meth(cls) -> None:
+                pass
+
+        class Foo:
+            def meth(self) -> None:
+                pass
+
+        pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match(
+            f"^{qualified_name(Foo)} is not compatible with the "
+            f"{MyProtocol.__qualname__} protocol because its 'meth' method 
should "
+            f"be a class method but it's an instance method"
+        )
 
 
 class TestRecursiveType:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/tests/test_importhook.py 
new/typeguard-4.4.0/tests/test_importhook.py
--- old/typeguard-4.3.0/tests/test_importhook.py        2024-05-27 
11:44:38.000000000 +0200
+++ new/typeguard-4.4.0/tests/test_importhook.py        2024-10-27 
11:55:53.000000000 +0100
@@ -64,5 +64,6 @@
     monkeypatch.setattr("typeguard.config.debug_instrumentation", True)
     import_dummymodule()
     out, err = capsys.readouterr()
-    assert f"Source code of '{dummy_module_path}' after instrumentation:" in 
err
+    path_str = str(dummy_module_path)
+    assert f"Source code of {path_str!r} after instrumentation:" in err
     assert "class DummyClass" in err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/typeguard-4.3.0/tests/test_typechecked.py 
new/typeguard-4.4.0/tests/test_typechecked.py
--- old/typeguard-4.3.0/tests/test_typechecked.py       2024-05-27 
11:44:38.000000000 +0200
+++ new/typeguard-4.4.0/tests/test_typechecked.py       2024-10-27 
11:55:53.000000000 +0100
@@ -619,9 +619,8 @@
     )
     assert process.returncode == expected_return_code
     if process.returncode == 1:
-        assert process.stderr.endswith(
-            b'typeguard.TypeCheckError: argument "x" (str) is not an instance 
of '
-            b"int\n"
+        assert process.stderr.strip().endswith(
+            b'typeguard.TypeCheckError: argument "x" (str) is not an instance 
of int'
         )
 
 

Reply via email to