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'
)