agronholm / typeguard

Run-time type checker for Python
Other
1.52k stars 114 forks source link

Crash in `check_mapping` when `items()` is a generator #419

Closed Kakadus closed 7 months ago

Kakadus commented 11 months ago

Things to check first

Typeguard version

4.1.5

Python version

3.10 / 3.11

What happened?

Using django's MultiValueDictcauses a crash because its .items() is a Generator. See django source.

check_mapping passes this generator to typeguard._config.CollectionCheckStrategy.iterate_samples: https://github.com/agronholm/typeguard/blob/128909a30e9ee4ff7b5cdd8218b7403f6cc4d0eb/src/typeguard/_checkers.py#L220-L222

This crashes it here, because generators have no len(): https://github.com/agronholm/typeguard/blob/128909a30e9ee4ff7b5cdd8218b7403f6cc4d0eb/src/typeguard/_config.py#L52-L59

How can we reproduce the bug?

Here is a minimal sample example. You can replace GeneratorDict with django's MultiValueDict to get the same result.

from typeguard import typechecked

@typechecked
def a(b: dict[str, list[str]]):
   ...

class GeneratorDict(dict):
    def items(self):
        for key in self:
            yield key, self[key]

a(GeneratorDict())

Running this code causes the following backtrace:

Traceback (most recent call last):
File "/.../test.py", line 15, in
a(MultiValueDict())
File "/.../test.py", line 5, in a
def a(b: dict[str, list[str]]) -> None:
File "/.../venv/lib/python3.11/site-packages/typeguard/_functions.py", line 138, in check_argument_types
check_type_internal(value, annotation, memo)
File "/.../venv/lib/python3.11/site-packages/typeguard/_checkers.py", line 759, in check_type_internal
checker(value, origin_type, args, memo)
File "/.../venv/lib/python3.11/site-packages/typeguard/_checkers.py", line 220, in check_mapping
samples = memo.config.collection_check_strategy.iterate_samples(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../venv/lib/python3.11/site-packages/typeguard/_config.py", line 54, in iterate_samples
if len(collection):
^^^^^^^^^^^^^^^
TypeError: object of type 'generator' has no len()