From bd6f82c74066eb48fe7e0cd7e67ac76d8d347e47 Mon Sep 17 00:00:00 2001 From: FelixTheC Date: Mon, 4 Dec 2023 21:50:45 +0100 Subject: [PATCH] feat: raise `UndefinedKey` exception on user decision (#130) --- pyproject.toml | 2 +- strongtyping/strong_typing.py | 11 +++++++++++ strongtyping/strong_typing_utils.py | 6 ++++++ strongtyping/tests/test_typedict.py | 19 ++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8562180..b2be022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ profile = "black" [tool.ruff] exclude = ["strongtyping/tests"] line-length = 100 -target-version = "py310" +target-version = "py312" [tool.poetry] name = "strongtyping" diff --git a/strongtyping/strong_typing.py b/strongtyping/strong_typing.py index ae51a34..3329043 100644 --- a/strongtyping/strong_typing.py +++ b/strongtyping/strong_typing.py @@ -10,6 +10,7 @@ from strongtyping.config import SEVERITY_LEVEL from strongtyping.strong_typing_utils import ( TypeMisMatch, + UndefinedKey, check_type, checking_typing_typedict_values, default_return_queue, @@ -235,6 +236,7 @@ def match_class_typing(cls=None, **kwargs): excep_raise = kwargs.pop("excep_raise", TypeMisMatch) cache_size = kwargs.pop("cache_size", 1) severity = kwargs.pop("severity", "env") + throw_on_undefined = kwargs.pop("throw_on_undefined", False) def __has_annotations__(obj): return hasattr(obj, "__annotations__") @@ -267,6 +269,7 @@ def __add_decorator(_cls): cache_size=cache_size, excep_raise=excep_raise, subclass=is_static, + throw_on_undefined=throw_on_undefined, ), ) except TypeError: @@ -275,6 +278,14 @@ def __add_decorator(_cls): def wrapper(some_cls): def inner(*args, **cls_kwargs): __add_decorator(some_cls) + if throw_on_undefined: + allowed_keys = some_cls.__annotations__.keys() + data = args[0] if args else cls_kwargs + if not all(arg in allowed_keys for arg in data): + raise UndefinedKey( + f"You can use the `TypedDict[{some_cls.__name__}]` " + f"only with the following attributes: `{', '.join(allowed_keys)}`" + ) return some_cls(*args, **cls_kwargs) inner._matches_class = True diff --git a/strongtyping/strong_typing_utils.py b/strongtyping/strong_typing_utils.py index 6f5ce6f..e8c5af1 100644 --- a/strongtyping/strong_typing_utils.py +++ b/strongtyping/strong_typing_utils.py @@ -44,6 +44,12 @@ def __init__(self, message): print(message) +class UndefinedKey(Exception): + def __init__(self, message): + super().__init__() + print(message) + + typing_base_class = typing._GenericAlias # type: ignore diff --git a/strongtyping/tests/test_typedict.py b/strongtyping/tests/test_typedict.py index 6da2bb2..0a82baa 100644 --- a/strongtyping/tests/test_typedict.py +++ b/strongtyping/tests/test_typedict.py @@ -9,7 +9,7 @@ import pytest from strongtyping.strong_typing import match_class_typing, match_typing -from strongtyping.strong_typing_utils import TypeMisMatch, ValidationError +from strongtyping.strong_typing_utils import TypeMisMatch, UndefinedKey, ValidationError def test_typedict(): @@ -305,5 +305,22 @@ def foo(**kwargs: Unpack[Movie]) -> str: foo(**movie) +def test_undefined_keys_raise_error(): + @match_class_typing(throw_on_undefined=True) + class User(TypedDict): + id: str + username: str + description: str | None + + with pytest.raises(UndefinedKey): + User({"id": "0123", "username": "test", "description": None, "age": 10}) + + with pytest.raises(UndefinedKey): + User(id="Alfonso CuarĂ³n", username="2004", description=None, age=10) + + assert User({"id": "0123", "username": "test", "description": None}) + assert User(id="0123", username="test") + + if __name__ == "__main__": pytest.main(["-vv", "-s", __file__])