FelixTheC / strongtyping

Decorator which checks whether the function is called with the correct type of parameters.
https://pypi.org/project/strongtyping/
108 stars 3 forks source link

More explicit TypeMisMatch errors #111

Open frallain opened 1 year ago

frallain commented 1 year ago

Python Version used Python 3.10.4 (main, May 28 2022, 13:25:38) [GCC 10.2.1 20210110] on linux

Package Version used strongtyping 3.10.4

Addon in use strongtyping_modules [yes/no]

To Reproduce

# Python 3.10.4 (main, May 28 2022, 13:25:38) [GCC 10.2.1 20210110] on linux
# pip install typing-extensions==4.4.0
# pip install strongtyping==3.10.4

from typing import TypedDict

from strongtyping.strong_typing import match_class_typing

@match_class_typing
class MyType(TypedDict):
    a: int
    b: int

parent = {"b": "4", "extra": "5"}
MyType(parent)

gives

Incorrect parameter: `{'b': '4',
 'extra': '5'}`
    required: {'a': <class 'int'>, 'b': <class 'int'>}
Traceback (most recent call last):
  File "/root/src/script.py", line 17, in <module>
    MyType(parent)
  File "/root/.local/lib/python3.10/site-packages/strongtyping/strong_typing.py", line 176, in __call__
    raise self.excep_raise(self.create_error_msg(arguments))
strongtyping.strong_typing_utils.TypeMisMatch

Expected behavior A clearer error message on which attributes are missing/extra/have wrong types e.g.

Missing parameter(s): 'a'
Extra parameter(s): 'extra'
Wrong-typed parameter(s): 'b' 

Traceback (most recent call last):
  File "/root/src/script.py", line 18, in <module>
    MyType(parent)
  File "/root/.local/lib/python3.10/site-packages/strongtyping/strong_typing.py", line 176, in __call__
    raise self.excep_raise(self.create_error_msg(arguments))
strongtyping.strong_typing_utils.TypeMisMatch

Thanks again for your work!!!

FelixTheC commented 1 year ago

I hope you agree on the following solution. This is an example error message

<class '__main__.Regisseur'>
    parameter `movie` is invalid:
    it contains value `<class '__main__.Movie'>` which is not of type `typing_extensions.Required[dict[strongtyping.strong_typing.Movie]]`

If you capture the exception you can access all interesting informations

@match_class_typing
class Movie(TypedDict):
    title: str
    year: NotRequired[int]

@match_class_typing
class Additional(TypedDict):
    name: str
    val: NotRequired[str]

@match_class_typing
class Regisseur(TypedDict):
    name: str
    movie: Required[dict[Movie]]
    year: Required[int]
    info: NotRequired[dict[Additional]]

def main():
    try:
        print(Regisseur(name="Alfonso Cuarón", movie=Movie, year=2004))
    except TypeMisMatch as err:
        print(err.exception_info.issues)
        print(err.param_values)
        print(err.failed_params)
        print(err.annotations)

if __name__ == "__main__":
    main()
err.exception_info.issues = defaultdict(<class 'dict'>, {'movie': {0: [(<strongtyping.strong_typing.MatchTypedDict object at 0x7eff912c4460>, typing_extensions.Required[dict[strongtyping.strong_typing.Movie]])]}})
err.param_values = dict_keys(['name', 'movie', 'year', 'info'])
err.failed_params = {'name': 'Alfonso Cuarón', 'movie': <strongtyping.strong_typing.MatchTypedDict object at 0x7eff912c4460>, 'year': 2004}
err.annotations = dict_values([<class 'str'>, typing_extensions.Required[dict[strongtyping.strong_typing.Movie]], typing_extensions.Required[int], typing_extensions.NotRequired[dict[strongtyping.strong_typing.Additional]]])
FelixTheC commented 1 year ago

@frallain what do you say to my suggestion??