Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when type checking class inheriting from typing.NamedTuple via pytest #175

Closed
alexander-held opened this issue Feb 16, 2021 · 6 comments

Comments

@alexander-held
Copy link

Describe the bug
Hi, thanks a lot for this extremely useful package! I see errors with classes inheriting from typing.NamedTuple with typeguard versions 2.11.0 / 2.11.1 and Python 3.6 / 3.7 when running via pytest. Python 3.8 and 3.9 do not show the same behavior. I am not sure whether this issue is unique to NamedTuple, but have only seen it there so far.

To Reproduce
Create a new environment with Python 3.6 or 3.7 (I am using docker, docker run -it --rm python:3.7-slim bash). Install pytest and typeguard:

$ pip install pytest typeguard
$ pip list
Package            Version
------------------ -------
attrs              20.3.0
importlib-metadata 3.4.0
iniconfig          1.1.1
packaging          20.9
pip                21.0.1
pluggy             0.13.1
py                 1.10.0
pyparsing          2.4.7
pytest             6.2.2
setuptools         53.0.0
toml               0.10.2
typeguard          2.11.1
typing-extensions  3.7.4.3
wheel              0.36.2
zipp               3.4.0

Now create a file example.py containing a type-checked class inheriting from typing.NamedTuple:

from typing import NamedTuple
import typeguard

@typeguard.typechecked
class C(NamedTuple):
    x: float

def test_C():
    C(1.0)

The test_C function is not strictly needed to reproduce the error, but added such that the next step would do something more useful if the error were not present.

Running pytest example.py results in the following:

========================================== test session starts ==========================================
platform linux -- Python 3.7.9, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /
plugins: typeguard-2.11.1
collected 0 items / 1 error

================================================ ERRORS =================================================
______________________________________ ERROR collecting example.py ______________________________________
example.py:5: in <module>
    class C(NamedTuple):
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:891: in typechecked
    property_func, always=always, _localns=func.__dict__
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:906: in typechecked
    warn('no type annotations present -- not typechecking {}'.format(function_name(func)))
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:253: in function_name
    module = func.__module__
E   AttributeError: 'operator.itemgetter' object has no attribute '__module__'
======================================== short test summary info ========================================
ERROR example.py - AttributeError: 'operator.itemgetter' object has no attribute '__module__'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================== 1 error in 0.15s ============================================

When downgrading to typeguard version 2.10.0, the test runs fine:

$ pip install typeguard==2.10.0
$ pytest example.py
========================================== test session starts ==========================================
platform linux -- Python 3.7.9, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /
plugins: typeguard-2.10.0
collected 1 item

example.py .                                                                                      [100%]

=========================================== 1 passed in 0.02s ===========================================

Expected behavior
I expect pytest to run without errors caused by typeguard, which is the case for version 2.10.0 and for Python versions 3.8 and 3.9.

Additional context
none

@agronholm
Copy link
Owner

agronholm commented Feb 16, 2021

The only use for @typechecked on a class is to automatically decorate all methods with it. Since the class has no annotated methods, you shouldn't try to do it.

If the documentation made it seem like typed attribute checking is supported, then I must apologize and ask that you point out where this was. There is an issue (#161) that tracks this feature, should it get implemented in the future.

All this said, it should not give you the error it does, so that at least is a bug.

@alexander-held
Copy link
Author

Sorry, I did not mean to imply that I expect the attribute checking to work properly. I use pytest --typeguard-packages=... in the setup where I first saw the issue, which automatically decorates everything.

I think I might be misunderstanding what is not supported. In the example above, the types of the fields seem to get checked. When testing the following with pytest

from typing import NamedTuple
import typeguard

@typeguard.typechecked
class C(NamedTuple):
    x: float

def test_C():
    C("a")

I get an error flagging the wrong use of a string where a float is expected:

example.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:890: in wrapper
    check_argument_types(memo)
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:748: in check_argument_types
    raise exc from None
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:746: in check_argument_types
    check_type(description, value, expected_type, memo)
usr/local/lib/python3.7/site-packages/typeguard/__init__.py:648: in check_type
    check_number(argname, value, expected_type)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

argname = 'argument "x"', value = 'a', expected_type = <class 'float'>

    def check_number(argname: str, value, expected_type):
        if expected_type is complex and not isinstance(value, (complex, float, int)):
            raise TypeError('type of {} must be either complex, float or int; got {} instead'.
                            format(argname, qualified_name(value.__class__)))
        elif expected_type is float and not isinstance(value, (float, int)):
            raise TypeError('type of {} must be either float or int; got {} instead'.
>                           format(argname, qualified_name(value.__class__)))
E           TypeError: type of argument "x" must be either float or int; got str instead

usr/local/lib/python3.7/site-packages/typeguard/__init__.py:541: TypeError

@agronholm
Copy link
Owner

Hm, now I'm confused. I genuinely did not expect this would work. Without the pytest plugin, it certainly doesn't work (thus making the explicit decorator unnecessary). I'll try to fix this ASAP.

@alexander-held
Copy link
Author

Thank you very much for looking into this!

@alexander-held
Copy link
Author

Type checking for NamedTuple seems to also work (at least in simple setups) without pytest:

module.py:

from typing import NamedTuple

class C(NamedTuple):
    x: float

example.py:

from typeguard.importhook import install_import_hook

install_import_hook("module")

import module

module.C("a")

execute:

$ python example.py
[...]
TypeError: type of argument "x" must be either float or int; got str instead

I understand it may be a coincidence that this works, and should not be relied upon if it is not supported officially.

muggenhor pushed a commit to tomtom-international/hopic that referenced this issue Feb 19, 2021
typeguard > 2.11.0 and 2.11.1 have problems with checking types derived from NamedTuple. Upstream issue: agronholm/typeguard#175
muggenhor pushed a commit to tomtom-international/hopic that referenced this issue Feb 19, 2021
fix: avoid using specific versions of typeguardtypeguard > 2.11.0 and 2.11.1 have problems with checking types derived from NamedTuple. Upstream issue: agronholm/typeguard#175

Caused-by: agronholm/typeguard#175
Acked-by: Giel van Schijndel <[email protected]>
Acked-by: Kevin de Jong (EHV) <[email protected]>
Merged-by: Hopic 1.32.1.dev15+ge9cf4a5
muggenhor pushed a commit to tomtom-international/hopic that referenced this issue Apr 7, 2021
typeguard >= 2.11 has problems with checking types derived from [NamedTuple].

[NamedTuple]: https://docs.python.org/3/library/typing.html#typing.NamedTuple

Caused-by: agronholm/typeguard#175
Acked-by: Giel van Schijndel <[email protected]>
Acked-by: Joost Muller <[email protected]>
Acked-by: Maikel van den Hurk <[email protected]>
Acked-by: Rene Kempen <[email protected]>
Merged-by: Hopic 1.38.0rc1
@agronholm
Copy link
Owner

This has been resolved in master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants