From 1fa68d8dd7fdbbea1092b30a42574b5af8cd1571 Mon Sep 17 00:00:00 2001 From: Willi Sontopski <32729196+LostInDarkMath@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:17:50 +0100 Subject: [PATCH 1/3] migrate from unittest to pytest --- .github/workflows/main.yml | 4 +- CHANGELOG.md | 4 + docs/pedantic/decorators/fn_deco_retry.html | 4 +- .../fn_deco_validate/validators/max.html | 18 +- .../fn_deco_validate/validators/min.html | 18 +- docs/pedantic/index.html | 5 - .../mixins/with_decorated_methods.html | 8 +- pedantic/decorators/fn_deco_retry.py | 2 +- .../fn_deco_validate/validators/max.py | 9 +- .../fn_deco_validate/validators/min.py | 9 +- pedantic/mixins/with_decorated_methods.py | 2 +- pedantic/tests/__init__.py | 1 - .../tests/test_assert_value_matches_type.py | 209 -- pedantic/tests/test_async_context_manager.py | 77 - pedantic/tests/test_context_manager.py | 77 - pedantic/tests/test_frozen_dataclass.py | 373 --- pedantic/tests/test_generator_wrapper.py | 33 - pedantic/tests/test_generic_mixin.py | 165 - pedantic/tests/test_in_subprocess.py | 152 - pedantic/tests/test_rename_kwargs.py | 18 - pedantic/tests/test_resolve_forward_ref.py | 28 - pedantic/tests/test_retry.py | 162 - pedantic/tests/test_with_decorated_methods.py | 125 - pedantic/tests/tests_class_decorators.py | 39 - .../tests/tests_combination_of_decorators.py | 166 - pedantic/tests/tests_decorated_function.py | 140 - pedantic/tests/tests_docstring.py | 481 --- pedantic/tests/tests_doctests.py | 34 - pedantic/tests/tests_environment_variables.py | 41 - pedantic/tests/tests_generator.py | 106 - pedantic/tests/tests_generic_classes.py | 317 -- pedantic/tests/tests_main.py | 123 - pedantic/tests/tests_mock.py | 13 - pedantic/tests/tests_pedantic.py | 2462 -------------- pedantic/tests/tests_pedantic_async.py | 58 - pedantic/tests/tests_pedantic_class.py | 537 ---- .../tests/tests_pedantic_class_docstring.py | 86 - pedantic/tests/tests_pedantic_python_311.py | 128 - pedantic/tests/tests_require_kwargs.py | 31 - .../tests/tests_small_method_decorators.py | 276 -- pedantic/tests/validate/test_convert_value.py | 56 - .../tests/validate/test_datetime_isoformat.py | 23 - .../tests/validate/test_flask_parameters.py | 609 ---- .../test_parameter_environment_variable.py | 85 - pedantic/tests/validate/test_validate.py | 428 --- .../validate/test_validator_composite.py | 23 - .../test_validator_datetime_unix_timestamp.py | 31 - .../tests/validate/test_validator_email.py | 28 - .../tests/validate/test_validator_for_each.py | 30 - .../tests/validate/test_validator_is_enum.py | 74 - .../tests/validate/test_validator_is_uuid.py | 34 - .../validate/test_validator_match_pattern.py | 22 - pedantic/tests/validate/test_validator_max.py | 36 - .../validate/test_validator_max_length.py | 32 - pedantic/tests/validate/test_validator_min.py | 36 - .../validate/test_validator_min_length.py | 32 - .../validate/test_validator_not_empty.py | 21 - poetry.lock | 263 +- pyproject.toml | 9 +- .../tests/validate => tests}/__init__.py | 0 tests/conftest.py | 12 + tests/decorators/__init__.py | 0 tests/decorators/pedantic/__init__.py | 0 tests/decorators/pedantic/test_generator.py | 113 + .../pedantic/test_generic_classes.py | 315 ++ tests/decorators/pedantic/test_pedantic.py | 2819 +++++++++++++++++ .../pedantic/test_pedantic_async.py | 65 + .../pedantic/test_pedantic_class.py | 565 ++++ .../pedantic/test_pedantic_class_docstring.py | 87 + .../decorators/test_async_context_manager.py | 84 + tests/decorators/test_class_decorators.py | 35 + .../test_combination_of_decorators.py | 174 + tests/decorators/test_context_manager.py | 80 + tests/decorators/test_docstring.py | 500 +++ tests/decorators/test_frozen_dataclass.py | 384 +++ tests/decorators/test_in_subprocess.py | 172 + tests/decorators/test_mock.py | 10 + tests/decorators/test_rename_kwargs.py | 15 + tests/decorators/test_require_kwargs.py | 29 + tests/decorators/test_retry_decorator.py | 89 + tests/decorators/test_retry_func.py | 82 + .../test_small_method_decorators.py | 311 ++ tests/decorators/validate/__init__.py | 0 .../decorators/validate/test_convert_value.py | 56 + .../validate/test_datetime_isoformat.py | 23 + .../validate/test_flask_parameters.py | 628 ++++ .../test_parameter_environment_variable.py | 87 + tests/decorators/validate/test_validate.py | 453 +++ .../validate/test_validator_composite.py | 22 + .../test_validator_datetime_unix_timestamp.py | 31 + .../validate/test_validator_email.py | 28 + .../validate/test_validator_for_each.py | 30 + .../validate/test_validator_is_enum.py | 78 + .../validate/test_validator_is_uuid.py | 35 + .../validate/test_validator_match_pattern.py | 21 + .../decorators/validate/test_validator_max.py | 35 + .../validate/test_validator_max_length.py | 31 + .../decorators/validate/test_validator_min.py | 36 + .../validate/test_validator_min_length.py | 31 + .../validate/test_validator_not_empty.py | 20 + tests/mixins/__init__.py | 0 tests/mixins/test_generic_mixin.py | 170 + tests/mixins/test_with_decorated_methods.py | 127 + tests/models/__init__.py | 0 tests/models/test_decorated_function.py | 142 + tests/models/test_generator_wrapper.py | 33 + tests/test_environment_variables.py | 35 + tests/test_the_tests.py | 19 + tests/type_checking_logic/__init__.py | 0 .../test_assert_value_matches_type.py | 216 ++ .../test_resolve_forward_ref.py | 28 + type_hint_parser_dependency_graph.graphml | 373 --- 112 files changed, 8666 insertions(+), 8476 deletions(-) delete mode 100644 pedantic/tests/__init__.py delete mode 100644 pedantic/tests/test_assert_value_matches_type.py delete mode 100644 pedantic/tests/test_async_context_manager.py delete mode 100644 pedantic/tests/test_context_manager.py delete mode 100644 pedantic/tests/test_frozen_dataclass.py delete mode 100644 pedantic/tests/test_generator_wrapper.py delete mode 100644 pedantic/tests/test_generic_mixin.py delete mode 100644 pedantic/tests/test_in_subprocess.py delete mode 100644 pedantic/tests/test_rename_kwargs.py delete mode 100644 pedantic/tests/test_resolve_forward_ref.py delete mode 100644 pedantic/tests/test_retry.py delete mode 100644 pedantic/tests/test_with_decorated_methods.py delete mode 100644 pedantic/tests/tests_class_decorators.py delete mode 100644 pedantic/tests/tests_combination_of_decorators.py delete mode 100644 pedantic/tests/tests_decorated_function.py delete mode 100644 pedantic/tests/tests_docstring.py delete mode 100644 pedantic/tests/tests_doctests.py delete mode 100644 pedantic/tests/tests_environment_variables.py delete mode 100644 pedantic/tests/tests_generator.py delete mode 100644 pedantic/tests/tests_generic_classes.py delete mode 100644 pedantic/tests/tests_main.py delete mode 100644 pedantic/tests/tests_mock.py delete mode 100644 pedantic/tests/tests_pedantic.py delete mode 100644 pedantic/tests/tests_pedantic_async.py delete mode 100644 pedantic/tests/tests_pedantic_class.py delete mode 100644 pedantic/tests/tests_pedantic_class_docstring.py delete mode 100644 pedantic/tests/tests_pedantic_python_311.py delete mode 100644 pedantic/tests/tests_require_kwargs.py delete mode 100644 pedantic/tests/tests_small_method_decorators.py delete mode 100644 pedantic/tests/validate/test_convert_value.py delete mode 100644 pedantic/tests/validate/test_datetime_isoformat.py delete mode 100644 pedantic/tests/validate/test_flask_parameters.py delete mode 100644 pedantic/tests/validate/test_parameter_environment_variable.py delete mode 100644 pedantic/tests/validate/test_validate.py delete mode 100644 pedantic/tests/validate/test_validator_composite.py delete mode 100644 pedantic/tests/validate/test_validator_datetime_unix_timestamp.py delete mode 100644 pedantic/tests/validate/test_validator_email.py delete mode 100644 pedantic/tests/validate/test_validator_for_each.py delete mode 100644 pedantic/tests/validate/test_validator_is_enum.py delete mode 100644 pedantic/tests/validate/test_validator_is_uuid.py delete mode 100644 pedantic/tests/validate/test_validator_match_pattern.py delete mode 100644 pedantic/tests/validate/test_validator_max.py delete mode 100644 pedantic/tests/validate/test_validator_max_length.py delete mode 100644 pedantic/tests/validate/test_validator_min.py delete mode 100644 pedantic/tests/validate/test_validator_min_length.py delete mode 100644 pedantic/tests/validate/test_validator_not_empty.py rename {pedantic/tests/validate => tests}/__init__.py (100%) create mode 100644 tests/conftest.py create mode 100644 tests/decorators/__init__.py create mode 100644 tests/decorators/pedantic/__init__.py create mode 100644 tests/decorators/pedantic/test_generator.py create mode 100644 tests/decorators/pedantic/test_generic_classes.py create mode 100644 tests/decorators/pedantic/test_pedantic.py create mode 100644 tests/decorators/pedantic/test_pedantic_async.py create mode 100644 tests/decorators/pedantic/test_pedantic_class.py create mode 100644 tests/decorators/pedantic/test_pedantic_class_docstring.py create mode 100644 tests/decorators/test_async_context_manager.py create mode 100644 tests/decorators/test_class_decorators.py create mode 100644 tests/decorators/test_combination_of_decorators.py create mode 100644 tests/decorators/test_context_manager.py create mode 100644 tests/decorators/test_docstring.py create mode 100644 tests/decorators/test_frozen_dataclass.py create mode 100644 tests/decorators/test_in_subprocess.py create mode 100644 tests/decorators/test_mock.py create mode 100644 tests/decorators/test_rename_kwargs.py create mode 100644 tests/decorators/test_require_kwargs.py create mode 100644 tests/decorators/test_retry_decorator.py create mode 100644 tests/decorators/test_retry_func.py create mode 100644 tests/decorators/test_small_method_decorators.py create mode 100644 tests/decorators/validate/__init__.py create mode 100644 tests/decorators/validate/test_convert_value.py create mode 100644 tests/decorators/validate/test_datetime_isoformat.py create mode 100644 tests/decorators/validate/test_flask_parameters.py create mode 100644 tests/decorators/validate/test_parameter_environment_variable.py create mode 100644 tests/decorators/validate/test_validate.py create mode 100644 tests/decorators/validate/test_validator_composite.py create mode 100644 tests/decorators/validate/test_validator_datetime_unix_timestamp.py create mode 100644 tests/decorators/validate/test_validator_email.py create mode 100644 tests/decorators/validate/test_validator_for_each.py create mode 100644 tests/decorators/validate/test_validator_is_enum.py create mode 100644 tests/decorators/validate/test_validator_is_uuid.py create mode 100644 tests/decorators/validate/test_validator_match_pattern.py create mode 100644 tests/decorators/validate/test_validator_max.py create mode 100644 tests/decorators/validate/test_validator_max_length.py create mode 100644 tests/decorators/validate/test_validator_min.py create mode 100644 tests/decorators/validate/test_validator_min_length.py create mode 100644 tests/decorators/validate/test_validator_not_empty.py create mode 100644 tests/mixins/__init__.py create mode 100644 tests/mixins/test_generic_mixin.py create mode 100644 tests/mixins/test_with_decorated_methods.py create mode 100644 tests/models/__init__.py create mode 100644 tests/models/test_decorated_function.py create mode 100644 tests/models/test_generator_wrapper.py create mode 100644 tests/test_environment_variables.py create mode 100644 tests/test_the_tests.py create mode 100644 tests/type_checking_logic/__init__.py create mode 100644 tests/type_checking_logic/test_assert_value_matches_type.py create mode 100644 tests/type_checking_logic/test_resolve_forward_ref.py delete mode 100644 type_hint_parser_dependency_graph.graphml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5826981..58639165 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,9 +23,9 @@ jobs: python -m pip install --upgrade pip pip install -U coveralls pip install -e .[dev] - - name: Run tests + - name: Run tests with pytest run: | - coverage run --branch pedantic/tests/tests_main.py + pytest --doctest-modules --cov=pedantic --cov-branch --cov-report= --cov-report=term - name: Coveralls uses: coverallsapp/github-action@v2.2.3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 79faf4b1..89631c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## Pedantic 2.4.0 +- migrate from unittest to pytest +- exclude tests from package + ## Pedantic 2.3.3 - [fixed validation error message in MinLength validator](https://github.com/LostInDarkMath/pedantic-python-decorators/issues/111) - removed unused scripts diff --git a/docs/pedantic/decorators/fn_deco_retry.html b/docs/pedantic/decorators/fn_deco_retry.html index 72c65185..c55e6503 100644 --- a/docs/pedantic/decorators/fn_deco_retry.html +++ b/docs/pedantic/decorators/fn_deco_retry.html @@ -73,7 +73,7 @@
>>> @retry(attempts=3, exceptions=(ValueError, TypeError))
... def foo():
... raise ValueError('Some error')
->>> foo()
+>>> foo() # doctest: +SKIP
diff --git a/docs/pedantic/decorators/fn_deco_validate/validators/max.html b/docs/pedantic/decorators/fn_deco_validate/validators/max.html
index 38045f83..419900da 100644
--- a/docs/pedantic/decorators/fn_deco_validate/validators/max.html
+++ b/docs/pedantic/decorators/fn_deco_validate/validators/max.html
@@ -59,11 +59,12 @@ Classes
def __init__(self, value: Union[int, float], include_boundary: bool = True) -> None:
"""
>>> Max(7, True).validate(7)
- True
- >>> Max(7, False).validate(7)
- False
+ 7
+ >>> Max(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ValidatorException: ...
>>> Max(7, False).validate(6.999)
- True
+ 6.999
"""
self._value = value
self._include_boundary = include_boundary
@@ -80,11 +81,12 @@ Classes
Helper class that provides a standard way to create an ABC using
inheritance.
>>> Max(7, True).validate(7)
-True
->>> Max(7, False).validate(7)
-False
+7
+>>> Max(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+ValidatorException: ...
>>> Max(7, False).validate(6.999)
-True
+6.999
Ancestors
diff --git a/docs/pedantic/decorators/fn_deco_validate/validators/min.html b/docs/pedantic/decorators/fn_deco_validate/validators/min.html
index 2a08f27e..c08eb3cd 100644
--- a/docs/pedantic/decorators/fn_deco_validate/validators/min.html
+++ b/docs/pedantic/decorators/fn_deco_validate/validators/min.html
@@ -59,11 +59,12 @@ Classes
def __init__(self, value: Union[int, float], include_boundary: bool = True) -> None:
"""
>>> Min(7, True).validate(7)
- True
- >>> Min(7, False).validate(7)
- False
+ 7
+ >>> Min(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ValidatorException: ...
>>> Min(7, False).validate(7.001)
- True
+ 7.001
"""
self._value = value
self._include_boundary = include_boundary
@@ -80,11 +81,12 @@ Classes
Helper class that provides a standard way to create an ABC using
inheritance.
>>> Min(7, True).validate(7)
-True
->>> Min(7, False).validate(7)
-False
+7
+>>> Min(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+ValidatorException: ...
>>> Min(7, False).validate(7.001)
-True
+7.001
Ancestors
diff --git a/docs/pedantic/index.html b/docs/pedantic/index.html
index 565c6388..da96160b 100644
--- a/docs/pedantic/index.html
+++ b/docs/pedantic/index.html
@@ -76,10 +76,6 @@ Sub-modules
-
-pedantic.tests
--
-
-
pedantic.type_checking_logic
-
@@ -109,7 +105,6 @@
Sub-modules
pedantic.helper_methods
pedantic.mixins
pedantic.models
-pedantic.tests
pedantic.type_checking_logic
diff --git a/docs/pedantic/mixins/with_decorated_methods.html b/docs/pedantic/mixins/with_decorated_methods.html
index e77e037e..f92feb20 100644
--- a/docs/pedantic/mixins/with_decorated_methods.html
+++ b/docs/pedantic/mixins/with_decorated_methods.html
@@ -117,10 +117,6 @@ Ancestors
- enum.ReprEnum
- enum.Enum
-Subclasses
-
-- Decorators
-
class WithDecoratedMethods
@@ -152,7 +148,7 @@ Subclasses
... def m3(self) -> None:
... print('bar')
>>> instance = MyClass()
- >>> instance.get_decorated_functions()
+ >>> instance.get_decorated_functions() # doctest: +SKIP
{
<Decorators.FOO: '_foo'>: {
<bound method MyClass.m1 of <__main__.MyClass object at 0x7fea7a6e2610>>: 42,
@@ -203,7 +199,7 @@ Example
... def m3(self) -> None:
... print('bar')
>>> instance = MyClass()
->>> instance.get_decorated_functions()
+>>> instance.get_decorated_functions() # doctest: +SKIP
{
<Decorators.FOO: '_foo'>: {
<bound method MyClass.m1 of <__main__.MyClass object at 0x7fea7a6e2610>>: 42,
diff --git a/pedantic/decorators/fn_deco_retry.py b/pedantic/decorators/fn_deco_retry.py
index 211006c2..10fdb75b 100644
--- a/pedantic/decorators/fn_deco_retry.py
+++ b/pedantic/decorators/fn_deco_retry.py
@@ -31,7 +31,7 @@ def retry(
>>> @retry(attempts=3, exceptions=(ValueError, TypeError))
... def foo():
... raise ValueError('Some error')
- >>> foo()
+ >>> foo() # doctest: +SKIP
"""
def decorator(func: C) -> C:
diff --git a/pedantic/decorators/fn_deco_validate/validators/max.py b/pedantic/decorators/fn_deco_validate/validators/max.py
index 19506116..c687d0f1 100644
--- a/pedantic/decorators/fn_deco_validate/validators/max.py
+++ b/pedantic/decorators/fn_deco_validate/validators/max.py
@@ -8,11 +8,12 @@ class Max(Validator):
def __init__(self, value: Union[int, float], include_boundary: bool = True) -> None:
"""
>>> Max(7, True).validate(7)
- True
- >>> Max(7, False).validate(7)
- False
+ 7
+ >>> Max(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ValidatorException: ...
>>> Max(7, False).validate(6.999)
- True
+ 6.999
"""
self._value = value
self._include_boundary = include_boundary
diff --git a/pedantic/decorators/fn_deco_validate/validators/min.py b/pedantic/decorators/fn_deco_validate/validators/min.py
index e5966bdc..8a8e3550 100644
--- a/pedantic/decorators/fn_deco_validate/validators/min.py
+++ b/pedantic/decorators/fn_deco_validate/validators/min.py
@@ -8,11 +8,12 @@ class Min(Validator):
def __init__(self, value: Union[int, float], include_boundary: bool = True) -> None:
"""
>>> Min(7, True).validate(7)
- True
- >>> Min(7, False).validate(7)
- False
+ 7
+ >>> Min(7, False).validate(7) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ValidatorException: ...
>>> Min(7, False).validate(7.001)
- True
+ 7.001
"""
self._value = value
self._include_boundary = include_boundary
diff --git a/pedantic/mixins/with_decorated_methods.py b/pedantic/mixins/with_decorated_methods.py
index 79bd478d..f5ecded3 100644
--- a/pedantic/mixins/with_decorated_methods.py
+++ b/pedantic/mixins/with_decorated_methods.py
@@ -68,7 +68,7 @@ class WithDecoratedMethods(ABC, Generic[DecoratorTypeVar], GenericMixin):
... def m3(self) -> None:
... print('bar')
>>> instance = MyClass()
- >>> instance.get_decorated_functions()
+ >>> instance.get_decorated_functions() # doctest: +SKIP
{
: {
>: 42,
diff --git a/pedantic/tests/__init__.py b/pedantic/tests/__init__.py
deleted file mode 100644
index 8b137891..00000000
--- a/pedantic/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pedantic/tests/test_assert_value_matches_type.py b/pedantic/tests/test_assert_value_matches_type.py
deleted file mode 100644
index 79a91046..00000000
--- a/pedantic/tests/test_assert_value_matches_type.py
+++ /dev/null
@@ -1,209 +0,0 @@
-import unittest
-from abc import ABC
-from dataclasses import dataclass
-from datetime import datetime
-from typing import Callable, Awaitable, Coroutine, Any, Union, Optional, Generic, TypeVar, Tuple
-
-from pedantic.exceptions import PedanticTypeCheckException
-from pedantic.type_checking_logic.check_types import assert_value_matches_type
-
-
-@dataclass
-class Foo:
- value: int
-
-
-class TestAssertValueMatchesType(unittest.TestCase):
- def test_callable(self):
- def _cb(foo: Foo) -> str:
- return str(foo.value)
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., str],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., int],
- err='',
- type_vars={},
- )
-
- def test_callable_return_type_none(self):
- def _cb(foo: Foo) -> None:
- return print(foo)
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., None],
- err='',
- type_vars={},
- )
-
- def test_callable_awaitable(self):
- async def _cb(foo: Foo) -> str:
- return str(foo.value)
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[str]],
- err='',
- type_vars={},
- )
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[Any]],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[int]],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., str],
- err='',
- type_vars={},
- )
-
- def test_callable_coroutine(self):
- async def _cb(foo: Foo) -> str:
- return str(foo.value)
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Coroutine[None, None, str]],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Coroutine[None, None, int]],
- err='',
- type_vars={},
- )
-
- def test_callable_awaitable_with_none_return_type(self):
- async def _cb(foo: Foo) -> None:
- print(foo)
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[None]],
- err='',
- type_vars={},
- )
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[Any]],
- err='',
- type_vars={},
- )
-
- def test_callable_with_old_union_type_hint(self):
- async def _cb(machine_id: str) -> Union[int, None]:
- return 42
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[Union[int, None]]],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[int]],
- err='',
- type_vars={},
- )
-
- def test_callable_with_new_union_type_hint(self):
- async def _cb(machine_id: str) -> int | None:
- return 42
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[int | None]],
- err='',
- type_vars={},
- )
-
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[Any]],
- err='',
- type_vars={},
- )
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- assert_value_matches_type(
- value=_cb,
- type_=Callable[..., Awaitable[int]],
- err='',
- type_vars={},
- )
-
- def test_forward_ref_inheritance(self):
- T = TypeVar('T')
-
- class State(Generic[T], ABC):
- pass
-
- class StateMachine(Generic[T], ABC):
- pass
-
- class MachineState(State['MachineStateMachine']):
- pass
-
- class OfflineMachineState(MachineState):
- pass
-
- class MachineStateMachine(StateMachine[MachineState]):
- pass
-
- assert_value_matches_type(
- value=OfflineMachineState(),
- type_=Optional['MachineState'],
- err='',
- type_vars={},
- context=locals(),
- )
-
- def test_tuple_with_ellipsis(self):
- """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/75 """
-
- assert_value_matches_type(
- value=(1, 2.0, 'hello'),
- type_=Tuple[Any, ...],
- err='',
- type_vars={},
- context=locals(),
- )
-
- def test_union_of_callable(self):
- """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/74 """
-
- assert_value_matches_type(
- value=datetime.now(),
- type_=Union[datetime, Callable[[], datetime]],
- err='',
- type_vars={},
- context=locals(),
- )
diff --git a/pedantic/tests/test_async_context_manager.py b/pedantic/tests/test_async_context_manager.py
deleted file mode 100644
index c5c6e4a9..00000000
--- a/pedantic/tests/test_async_context_manager.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import unittest
-
-from pedantic.decorators import safe_async_contextmanager
-
-
-class TestAsyncContextManager(unittest.IsolatedAsyncioTestCase):
- async def test_safe_context_manager_no_exception(self):
- before = False
- after = False
-
- @safe_async_contextmanager
- async def foo():
- nonlocal before, after
- before = True
- yield 42
- after = True
-
- self.assertFalse(before)
- self.assertFalse(after)
-
- async with foo() as f:
- self.assertTrue(before)
- self.assertFalse(after)
- self.assertEqual(42, f)
-
- self.assertTrue(before)
- self.assertTrue(after)
-
- async def test_safe_context_manager_with_exception(self):
- before = False
- after = False
-
- @safe_async_contextmanager
- async def foo():
- nonlocal before, after
- before = True
- yield 42
- after = True
-
- self.assertFalse(before)
- self.assertFalse(after)
-
- with self.assertRaises(expected_exception=ValueError):
- async with foo() as f:
- self.assertTrue(before)
- self.assertFalse(after)
- self.assertEqual(42, f)
- raise ValueError('oh no')
-
- self.assertTrue(before)
- self.assertTrue(after)
-
- async def test_safe_context_manager_with_args_kwargs(self):
- @safe_async_contextmanager
- async def foo(a, b):
- yield a, b
-
- async with foo(42, b=43) as f:
- self.assertEqual((42, 43), f)
-
- def test_safe_context_manager_async(self):
- with self.assertRaises(expected_exception=AssertionError) as e:
- @safe_async_contextmanager
- def foo(a, b):
- yield a, b
-
- expected = 'foo is not an async generator. So you need to use "safe_contextmanager" instead.'
- self.assertEqual(expected, e.exception.args[0])
-
- async def test_safe_context_manager_non_generator(self):
- with self.assertRaises(expected_exception=AssertionError) as e:
- @safe_async_contextmanager
- async def foo(a, b):
- return a, b
-
- expected = 'foo is not a generator.'
- self.assertEqual(expected, e.exception.args[0])
diff --git a/pedantic/tests/test_context_manager.py b/pedantic/tests/test_context_manager.py
deleted file mode 100644
index 7162e426..00000000
--- a/pedantic/tests/test_context_manager.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators import safe_contextmanager
-
-
-class TestContextManager(TestCase):
- def test_safe_context_manager_no_exception(self):
- before = False
- after = False
-
- @safe_contextmanager
- def foo():
- nonlocal before, after
- before = True
- yield 42
- after = True
-
- self.assertFalse(before)
- self.assertFalse(after)
-
- with foo() as f:
- self.assertTrue(before)
- self.assertFalse(after)
- self.assertEqual(42, f)
-
- self.assertTrue(before)
- self.assertTrue(after)
-
- def test_safe_context_manager_with_exception(self):
- before = False
- after = False
-
- @safe_contextmanager
- def foo():
- nonlocal before, after
- before = True
- yield 42
- after = True
-
- self.assertFalse(before)
- self.assertFalse(after)
-
- with self.assertRaises(expected_exception=ValueError):
- with foo() as f:
- self.assertTrue(before)
- self.assertFalse(after)
- self.assertEqual(42, f)
- raise ValueError('oh no')
-
- self.assertTrue(before)
- self.assertTrue(after)
-
- def test_safe_context_manager_with_args_kwargs(self):
- @safe_contextmanager
- def foo(a, b):
- yield a, b
-
- with foo(42, b=43) as f:
- self.assertEqual((42, 43), f)
-
- def test_safe_context_manager_async(self):
- with self.assertRaises(expected_exception=AssertionError) as e:
- @safe_contextmanager
- async def foo(a, b):
- yield a, b
-
- expected = 'foo is async. So you need to use "safe_async_contextmanager" instead.'
- self.assertEqual(expected, e.exception.args[0])
-
- def test_safe_context_manager_non_generator(self):
- with self.assertRaises(expected_exception=AssertionError) as e:
- @safe_contextmanager
- def foo(a, b):
- return a, b
-
- expected = 'foo is not a generator.'
- self.assertEqual(expected, e.exception.args[0])
diff --git a/pedantic/tests/test_frozen_dataclass.py b/pedantic/tests/test_frozen_dataclass.py
deleted file mode 100644
index a5ec5c04..00000000
--- a/pedantic/tests/test_frozen_dataclass.py
+++ /dev/null
@@ -1,373 +0,0 @@
-import unittest
-from abc import ABC
-from dataclasses import dataclass, FrozenInstanceError
-from typing import List, Dict, Set, Tuple, Awaitable, Callable, Generic, TypeVar, Optional
-
-from pedantic.decorators.cls_deco_frozen_dataclass import frozen_dataclass, frozen_type_safe_dataclass
-from pedantic.exceptions import PedanticTypeCheckException
-
-
-@frozen_dataclass
-class Foo:
- a: int
- b: str
- c: bool
-
-
-@frozen_type_safe_dataclass
-class B:
- v: Set[int]
-
-
-@frozen_type_safe_dataclass
-class A:
- foo: List[int]
- bar: Dict[str, str]
- values: Tuple[B, B]
-
-
-class TestFrozenDataclass(unittest.TestCase):
- def test_equals_and_hash(self):
- a = Foo(a=6, b='hi', c=True)
- b = Foo(a=6, b='hi', c=True)
- c = Foo(a=7, b='hi', c=True)
-
- self.assertEqual(a, b)
- self.assertEqual(hash(a), hash(b))
-
- self.assertNotEqual(a, c)
- self.assertNotEqual(hash(a), hash(c))
-
- def test_copy_with(self):
- foo = Foo(a=6, b='hi', c=True)
-
- copy_1 = foo.copy_with()
- self.assertEqual(foo, copy_1)
-
- copy_2 = foo.copy_with(a=42)
- self.assertNotEqual(foo, copy_2)
- self.assertEqual(42, copy_2.a)
- self.assertEqual(foo.b, copy_2.b)
- self.assertEqual(foo.c, copy_2.c)
-
- copy_3 = foo.copy_with(b='Hello')
- self.assertNotEqual(foo, copy_3)
- self.assertEqual(foo.a, copy_3.a)
- self.assertEqual('Hello', copy_3.b)
- self.assertEqual(foo.c, copy_3.c)
-
- copy_4 = foo.copy_with(c=False)
- self.assertNotEqual(foo, copy_4)
- self.assertEqual(foo.a, copy_4.a)
- self.assertEqual(foo.b, copy_4.b)
- self.assertEqual(False, copy_4.c)
-
- copy_5 = foo.copy_with(a=676676, b='new', c=False)
- self.assertNotEqual(foo, copy_5)
- self.assertEqual(676676, copy_5.a)
- self.assertEqual('new', copy_5.b)
- self.assertEqual(False, copy_5.c)
-
- def test_validate_types(self):
- foo = Foo(a=6, b='hi', c=True)
- foo.validate_types()
-
- bar = Foo(a=6.6, b='hi', c=True)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException) as exc:
- bar.validate_types()
-
- expected = 'In dataclass "Foo" in field "a": Type hint is incorrect: Argument 6.6 of type ' \
- 'does not match expected type .'
- self.assertEqual(str(exc.exception), expected)
-
- def test_frozen_dataclass_above_dataclass(self):
- # This is the same behavior like
- # >>> @dataclass(frozen=True)
- # ... @dataclass
- # ... class C:
- # ... foo: int
-
- @frozen_dataclass
- @dataclass
- class A:
- foo: int
-
- with self.assertRaises(expected_exception=TypeError):
- A()
-
- with self.assertRaises(expected_exception=FrozenInstanceError):
- A(foo=3)
-
- def test_frozen_dataclass_below_dataclass(self):
- @dataclass
- @frozen_dataclass
- class A:
- foo: int
-
- with self.assertRaises(expected_exception=TypeError):
- A()
-
- a = A(foo=3)
-
- with self.assertRaises(expected_exception=FrozenInstanceError):
- a.foo = 4
-
- b = a.copy_with(foo=4)
- self.assertEqual(4, b.foo)
-
-
- def test_frozen_typesafe_dataclass_with_post_init(self):
- b = 3
-
- @frozen_dataclass(type_safe=True)
- class A:
- foo: int
-
- def __post_init__(self) -> None:
- nonlocal b
- b = 33
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException) as exc:
- A(foo=42.7)
-
- self.assertEqual(
- 'In dataclass "A" in field "foo": Type hint is incorrect: Argument 42.7 of type'
- ' does not match expected type .',
- str(exc.exception)
- )
-
- # we check that the __post_init__ method is executed
- self.assertEqual(33, b)
-
- def test_frozen_typesafe_dataclass_without_post_init(self):
- @frozen_dataclass(type_safe=True)
- class A:
- foo: int
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException) as exc:
- A(foo=42.7)
-
- self.assertEqual(
- 'In dataclass "A" in field "foo": Type hint is incorrect: Argument 42.7 of type'
- ' does not match expected type .',
- str(exc.exception)
- )
-
- def test_frozen_dataclass_with_empty_braces(self):
- @frozen_dataclass()
- class A:
- foo: int
-
- a = A(foo=42)
- self.assertEqual(42, a.foo)
-
- def test_frozen_dataclass_no_braces(self):
- @frozen_dataclass
- class A:
- foo: int
-
- a = A(foo=42)
- self.assertEqual(42, a.foo)
-
- def test_frozen_dataclass_order(self):
- @frozen_dataclass(order=True)
- class A:
- foo: int
- bar: int
-
- a = A(foo=42, bar=43)
- b = A(foo=42, bar=42)
- c = A(foo=41, bar=44)
- d = A(foo=44, bar=0)
- self.assertLess(b, a)
- self.assertLess(c, b)
- self.assertLess(a, d)
-
- def test_frozen_type_safe_dataclass_copy_with_check(self):
- @frozen_type_safe_dataclass
- class A:
- foo: int
- bar: bool
-
- a = A(foo=42, bar=False)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- a.copy_with(foo=1.1)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- a.copy_with(bar=11)
-
- a.copy_with(foo=11, bar=True)
-
- def test_copy_with_is_shallow(self):
- a = A(foo=[1, 2], bar={'hello': 'world'}, values=(B(v={4, 5}), B(v={6})))
- shallow = a.copy_with()
-
- # manipulation
- shallow.bar['hello'] = 'pedantic'
- shallow.foo.append(3)
-
- self.assertEqual([1, 2, 3], a.foo)
- self.assertEqual([1, 2, 3], shallow.foo)
- self.assertEqual('pedantic', a.bar['hello'])
- self.assertEqual('pedantic', shallow.bar['hello'])
-
- def test_copy_with_and_deep_copy_with(self):
- a = A(foo=[1, 2], bar={'hello': 'world'}, values=(B(v={4, 5}), B(v={6})))
- deep = a.deep_copy_with()
-
- # manipulation
- deep.bar['hello'] = 'pedantic'
- deep.foo.append(3)
-
- self.assertEqual([1, 2], a.foo)
- self.assertEqual([1, 2, 3], deep.foo)
- self.assertEqual('world', a.bar['hello'])
- self.assertEqual('pedantic', deep.bar['hello'])
-
- def test_frozen_dataclass_inheritance_override_post_init(self):
- i = 1
-
- @frozen_type_safe_dataclass
- class A:
- bar: int
-
- def __post_init__(self):
- nonlocal i
- i += 1
- print('hello a')
-
- @frozen_type_safe_dataclass
- class B(A):
- foo: int
-
- def __post_init__(self):
- nonlocal i
- i *= 10
- print('hello b')
-
- A(bar=3)
- self.assertEqual(2, i)
-
- b = B(bar=3, foo=42)
- self.assertEqual(20, i) # post init of A was not called
- self.assertEqual(3, b.bar)
- self.assertEqual(42, b.foo)
-
- a = b.copy_with()
- self.assertEqual(b, a)
- self.assertEqual(200, i)
-
- def test_frozen_dataclass_inheritance_not_override_post_init(self):
- i = 1
-
- @frozen_type_safe_dataclass
- class A:
- bar: int
-
- def __post_init__(self):
- nonlocal i
- i += 1
- print('hello a')
-
- @frozen_type_safe_dataclass
- class B(A):
- foo: int
-
- A(bar=3)
- self.assertEqual(2, i)
-
- b = B(bar=3, foo=42)
- self.assertEqual(3, i) # post init of A was called
- self.assertEqual(3, b.bar)
- self.assertEqual(42, b.foo)
-
- a = b.copy_with()
- self.assertEqual(b, a)
- self.assertEqual(4, i)
-
- def test_type_safe_frozen_dataclass_with_awaitable(self):
- @frozen_type_safe_dataclass
- class A:
- f: Callable[..., Awaitable[int]]
-
- async def _cb() -> int:
- return 42
-
- async def _cb_2() -> str:
- return '42'
-
- A(f=_cb)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- A(f=_cb_2)
-
- def test_type_safe_frozen_dataclass_with_forward_ref(self):
- T = TypeVar('T')
-
- class State(Generic[T], ABC):
- pass
-
- class StateMachine(Generic[T], ABC):
- pass
-
- @frozen_type_safe_dataclass
- class StateChangeResult:
- new_state: Optional['MachineState']
-
- class MachineState(State['MachineStateMachine']):
- pass
-
- class OfflineMachineState(MachineState):
- pass
-
- class OnlineMachineState:
- pass
-
- class MachineStateMachine(StateMachine[MachineState]):
- pass
-
- s = StateChangeResult(new_state=OfflineMachineState())
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- StateChangeResult(new_state=OnlineMachineState())
-
- s.validate_types()
-
- def test_forward_ref_to_itself(self):
- """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/72 """
-
- @frozen_type_safe_dataclass
- class Comment:
- replies: List['Comment']
-
- comment = Comment(replies=[Comment(replies=[])])
- comment.copy_with(replies=[Comment(replies=[])])
- comment.validate_types()
-
- def test_forward_ref_to_itself_while_class_not_in_scope(self):
- """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/72 """
-
- def _scope():
- @frozen_type_safe_dataclass
- class Comment:
- replies: List['Comment']
-
- def _make(replies=None):
- return Comment(replies=replies or [])
-
- return _make
-
- make = _scope()
-
- comment = make(replies=[make(replies=[])])
- comment.copy_with(replies=[make(replies=[])])
- comment.validate_types()
-
- def test_slots_work_with_equals(self):
- @frozen_dataclass(slots=True)
- class Foo:
- a: int
-
- o = Foo(a=0)
- assert o == o.copy_with()
diff --git a/pedantic/tests/test_generator_wrapper.py b/pedantic/tests/test_generator_wrapper.py
deleted file mode 100644
index 24256442..00000000
--- a/pedantic/tests/test_generator_wrapper.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Iterator
-from unittest import TestCase
-
-from pedantic.models import GeneratorWrapper
-
-
-class TestGeneratorWrapper(TestCase):
- def test_generator_wrapper(self) -> None:
- def gen_func() -> Iterator[int]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- generator = gen_func()
-
- g = GeneratorWrapper(
- wrapped=generator,
- expected_type=Iterator[int],
- err_msg='msg',
- type_vars={},
- )
-
- print(sum([x for x in g]))
-
- with self.assertRaises(expected_exception=Exception):
- g.throw(Exception('error'))
-
- with self.assertRaises(expected_exception=AttributeError):
- g.invalid
-
- g.close()
diff --git a/pedantic/tests/test_generic_mixin.py b/pedantic/tests/test_generic_mixin.py
deleted file mode 100644
index 7211dbad..00000000
--- a/pedantic/tests/test_generic_mixin.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import unittest
-from typing import TypeVar, Generic, List, Type
-
-from pedantic import GenericMixin
-
-A = TypeVar('A')
-E = TypeVar('E')
-S = TypeVar('S')
-T = TypeVar('T')
-U = TypeVar('U')
-
-
-class TestGenericMixin(unittest.TestCase):
- def test_single_type_var(self):
- class Foo(Generic[T], GenericMixin):
- value: T
-
- foo = Foo[str]()
- assert foo.type_var == str
- assert foo.type_vars == {T: str}
-
- invalid = Foo()
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- invalid.type_var
-
- assert f'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.exception.args[0]
-
- def test_multiple_type_vars(self):
- class Foo(Generic[T, U], GenericMixin):
- value: T
- values: List[U]
-
- foo = Foo[str, int]()
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- foo.type_var
-
- self.assertEqual(err.exception.args[0], 'You have multiple type parameters. '
- 'Please use "type_vars" instead of "type_var".')
-
- assert foo.type_vars == {T: str, U: int}
-
- invalid = Foo()
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- invalid.type_var
-
- assert f'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.exception.args[0]
-
- def test_non_generic_class(self):
- class Foo(GenericMixin):
- value: int
-
- invalid = Foo()
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- invalid.type_var
-
- self.assertEqual(err.exception.args[0], f'Foo is not a generic class. To make it generic, declare it like: '
- f'class Foo(Generic[T], GenericMixin):...')
-
- def test_call_type_var_in_constructor(self):
- class Foo(Generic[T], GenericMixin):
- def __init__(self) -> None:
- self.x = self.type_var()
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- Foo[str]()
-
- assert 'make sure that you do not call this in the __init__() method' in err.exception.args[0]
-
- def test_subclass_set_type_variable(self):
- class Gen(Generic[T], GenericMixin):
- def __init__(self, value: T) -> None:
- self.value = value
-
- def get_type(self) -> dict[TypeVar, Type]:
- return self.type_vars
-
- class MyClass(Gen[int]):
- pass
-
- bar = Gen[int](value=4)
- assert bar.get_type() == {T: int}
-
- foo = MyClass(value=4)
- assert foo.get_type() == {T: int}
-
- def test_subclass_with_multiple_parents(self):
- class Gen(Generic[T], GenericMixin):
- def __init__(self, value: T) -> None:
- self.value = value
-
- def get_type(self) -> dict[TypeVar, Type]:
- return self.type_vars
-
- class MyMixin:
- value = 42
-
- class MyClass(MyMixin, Gen[int]):
- pass
-
- bar = Gen[int](value=4)
- assert bar.get_type() == {T: int}
-
- foo = MyClass(value=4)
- assert foo.get_type() == {T: int}
-
- def test_resolved_type_var_inheritance(self):
- class Foo(Generic[T]): ...
-
- class Bar(Foo[int], Generic[U], GenericMixin): ...
-
- bar = Bar[str]()
- assert bar.type_vars == {T: int, U: str}
-
- def test_resolved_type_var_inheritance_2(self):
- class Foo(Generic[T], GenericMixin): ...
-
- class Bar(Foo[int], Generic[U]): ...
-
- bar = Bar[str]()
- assert bar.type_vars == {T: int, U: str}
-
- def test_very_complex_inheritance(self):
- class Foo(Generic[E], GenericMixin): ...
- class Bar(Foo[int], Generic[S]): ...
- class Baz(Foo[int]): ...
- class Deep(Baz): ...
- class Deeper(Baz, Generic[T]): ...
-
- foo = Foo[str]()
- actual = foo.type_vars
- assert actual == {E: str}
-
- bar = Bar[str]()
- actual = bar.type_vars
- assert actual == {E: int, S: str}
-
- baz = Baz()
- actual = baz.type_vars
- assert actual == {E: int}
-
- deep = Deep()
- actual = deep.type_vars
- assert actual == {E: int}
-
- deeper = Deeper[bool]()
- actual = deeper.type_vars
- assert actual == {E: int, T: bool}
-
- with self.assertRaises(expected_exception=AssertionError) as err:
- Foo().type_vars
-
- assert 'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.exception.args[0]
-
- def test_substitution_lookup_hits(self):
- class Base(Generic[A], GenericMixin): ...
- class Mid(Base[A], Generic[A]): ...
- class Final(Mid[int]): ...
-
- obj = Final()
- actual = obj.type_vars
- assert actual == {A: int}
diff --git a/pedantic/tests/test_in_subprocess.py b/pedantic/tests/test_in_subprocess.py
deleted file mode 100644
index f3f8d3ed..00000000
--- a/pedantic/tests/test_in_subprocess.py
+++ /dev/null
@@ -1,152 +0,0 @@
-import asyncio
-import time
-import unittest
-from typing import NoReturn
-
-from multiprocess import Pipe
-
-from pedantic import in_subprocess
-from pedantic.decorators.fn_deco_in_subprocess import _inner, SubprocessError
-
-
-class TestInSubprocess(unittest.IsolatedAsyncioTestCase):
- async def test_in_subprocess_simple(self):
- @in_subprocess
- def f() -> int:
- return 42
-
- assert await f() == 42
-
- async def test_in_subprocess_custom_object(self):
- class Foo:
- def __init__(self, v) -> None:
- self._value = v
-
- @in_subprocess
- def f() -> Foo:
- return Foo(v=42)
-
- assert (await f())._value == 42
-
- async def test_in_subprocess_simple_async(self):
- @in_subprocess
- async def f() -> int:
- return 42
-
- assert await f() == 42
-
- async def test_in_subprocess_no_args(self):
- @in_subprocess
- def f() -> int:
- time.sleep(0.1)
- return 42
-
- async def t() -> None:
- for _ in range(6):
- await asyncio.sleep(0.01)
- nonlocal counter
- counter += 1
-
- counter = 0
- task = asyncio.create_task(t())
- assert await f() == 42
- assert counter >= 5
- await task
-
- async def test_in_subprocess_no_args_no_return(self):
- @in_subprocess
- def f() -> None:
- time.sleep(0.1)
-
- assert await f() is None
-
- async def test_in_subprocess_exception(self):
- @in_subprocess
- def f() -> NoReturn:
- raise RuntimeError('foo')
-
- with self.assertRaises(expected_exception=RuntimeError):
- await f()
-
- async def test_not_in_subprocess_blocks(self):
- async def f() -> int:
- time.sleep(0.1)
- return 42
-
- async def t() -> None:
- for _ in range(6):
- await asyncio.sleep(0.05)
- nonlocal counter
- counter += 1
-
- counter = 0
- task = asyncio.create_task(t())
- assert await f() == 42
- assert counter == 0
- await task
-
- async def test_in_subprocess_with_arguments(self):
- @in_subprocess
- def f(a: int, b: int) -> int:
- return a + b
-
- assert await f(4, 5) == 9
- assert await f(a=4, b=5) == 9
-
- def test_inner_function_sync(self):
- """ Needed for line coverage"""
-
- rx, tx = Pipe(duplex=False)
- _inner(tx, lambda x: 1 / x, x=42)
- assert rx.recv() == 1 / 42
-
- _inner(tx, lambda x: 1 / x, x=0)
- ex = rx.recv()
- assert isinstance(ex, SubprocessError)
-
- def test_inner_function_async(self):
- """ Needed for line coverage"""
-
- async def foo(x):
- return 1/x
-
- rx, tx = Pipe(duplex=False)
- _inner(tx, foo, x=42)
- assert rx.recv() == 1 / 42
-
- _inner(tx, foo, x=0)
- ex = rx.recv()
- assert isinstance(ex, SubprocessError)
-
- async def test_in_subprocess_instance_method(self):
- class Foo:
- async def pos_args(self) -> int:
- return await self.f(4, 5)
-
- async def kw_args(self) -> int:
- return await self.f(a=4, b=5)
-
- @in_subprocess
- def f(self, a: int, b: int) -> int:
- return a + b
-
- foo = Foo()
- assert await foo.pos_args() == 9
- assert await foo.kw_args() == 9
-
- async def test_in_subprocess_static_method(self):
- class Foo:
- async def pos_args(self) -> int:
- return await self.f(4, 5)
-
- async def kw_args(self) -> int:
- return await self.f(a=4, b=5)
-
- @staticmethod
- @in_subprocess
- def f(a: int, b: int) -> int:
- return a + b
-
- foo = Foo()
- assert await foo.pos_args() == 9
- assert await foo.kw_args() == 9
diff --git a/pedantic/tests/test_rename_kwargs.py b/pedantic/tests/test_rename_kwargs.py
deleted file mode 100644
index aca62679..00000000
--- a/pedantic/tests/test_rename_kwargs.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import unittest
-
-from pedantic import rename_kwargs, Rename
-
-
-class TestRenameKwargs(unittest.TestCase):
- def test_rename_kwargs(self):
- @rename_kwargs(
- Rename(from_='x', to='a'),
- Rename(from_='y', to='b'),
- )
- def operation(a: int, b: int) -> int:
- return a + b
-
- operation(4, 5)
- operation(a=4, b=5)
- operation(x=4, y=5)
- operation(x=4, b=5)
diff --git a/pedantic/tests/test_resolve_forward_ref.py b/pedantic/tests/test_resolve_forward_ref.py
deleted file mode 100644
index 7f67841e..00000000
--- a/pedantic/tests/test_resolve_forward_ref.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import List, Optional, Union
-from unittest import TestCase
-
-from pedantic.type_checking_logic.resolve_forward_ref import resolve_forward_ref
-
-
-class TestResolveForwardRef(TestCase):
- def test_resolve_forward_ref_primitive_types(self):
- assert resolve_forward_ref(type_='int') == int
- assert resolve_forward_ref(type_='float') == float
- assert resolve_forward_ref(type_='str') == str
- assert resolve_forward_ref(type_='bool') == bool
-
- def test_resolve_forward_ref_typing_types(self):
- assert resolve_forward_ref(type_='List[int]') == List[int]
- assert resolve_forward_ref(type_='Optional[List[Union[str, float]]]') == Optional[List[Union[str, float]]]
-
- def test_unresolvable_type(self):
- with self.assertRaises(NameError):
- resolve_forward_ref(type_='Invalid')
-
- def test_resolve_forward_ref_custom_class(self):
- class Foo:
- pass
-
- context = locals()
- assert resolve_forward_ref(type_='Foo', context=context) == Foo
- assert resolve_forward_ref(type_='Optional[Foo]', context=context) == Optional[Foo]
diff --git a/pedantic/tests/test_retry.py b/pedantic/tests/test_retry.py
deleted file mode 100644
index 197977a2..00000000
--- a/pedantic/tests/test_retry.py
+++ /dev/null
@@ -1,162 +0,0 @@
-import unittest
-
-from pedantic import retry
-from pedantic.decorators.fn_deco_retry import retry_func
-
-
-class TestRetry(unittest.TestCase):
- def test_retry_positive_no_args(self):
- count = 0
-
- @retry(attempts=5)
- def foo():
- nonlocal count
- count += 1
-
- foo()
- assert count == 1
-
- def test_retry_positive_args_and_kwargs(self):
- count = 0
-
- @retry(attempts=5)
- def foo(x, y):
- nonlocal count
- count += x + y
-
- foo(12, y=42)
- assert count == 54
-
- def test_retry_positive_no_args_fails_every_time(self):
- count = 0
-
- @retry(attempts=5)
- def foo():
- nonlocal count
- count += 1
- raise ValueError('foo')
-
- with self.assertRaises(ValueError):
- foo()
-
- assert count == 5
-
- def test_retry_positive_no_args_fails_different_exception_type(self):
- count = 0
-
- @retry(attempts=5, exceptions=AssertionError)
- def foo():
- nonlocal count
- count += 1
- raise ValueError('foo')
-
- with self.assertRaises(ValueError):
- foo()
-
- assert count == 1
-
- def test_retry_fails_same_exception_type(self):
- count = 0
-
- @retry(attempts=5, exceptions=AssertionError)
- def foo():
- nonlocal count
- count += 1
- raise AssertionError('foo')
-
- with self.assertRaises(AssertionError):
- foo()
-
- assert count == 5
-
- def test_retry_positive_no_args_fails_on_first_times(self):
- count = 0
-
- @retry(attempts=5)
- def foo() -> int:
- nonlocal count
- count += 1
-
- if count < 3:
- raise ValueError('foo')
-
- return count
-
- assert foo() == 3
- assert count == 3
-
-
-class TestRetryFunc(unittest.TestCase):
- def test_retry_positive_no_args(self):
- count = 0
-
- def foo():
- nonlocal count
- count += 1
-
- retry_func(func=foo, attempts=5)
- assert count == 1
-
- def test_retry_positive_args_and_kwargs(self):
- count = 0
-
- def foo(x, y):
- nonlocal count
- count += x + y
-
- retry_func(foo, 12, attempts=5, y=42)
- assert count == 54
-
- def test_retry_positive_no_args_fails_every_time(self):
- count = 0
-
- def foo():
- nonlocal count
- count += 1
- raise ValueError('foo')
-
- with self.assertRaises(ValueError):
- retry_func(func=foo, attempts=5)
-
- assert count == 5
-
- def test_retry_positive_no_args_fails_different_exception_type(self):
- count = 0
-
- def foo():
- nonlocal count
- count += 1
- raise ValueError('foo')
-
- with self.assertRaises(ValueError):
- retry_func(func=foo, attempts=5, exceptions=AssertionError)
-
- assert count == 1
-
- def test_retry_fails_same_exception_type(self):
- count = 0
-
- def foo():
- nonlocal count
- count += 1
- raise AssertionError('foo')
-
- with self.assertRaises(AssertionError):
- retry_func(func=foo, attempts=5, exceptions=AssertionError)
-
- assert count == 5
-
- def test_retry_positive_no_args_fails_on_first_times(self):
- count = 0
-
- def foo() -> int:
- nonlocal count
- count += 1
-
- if count < 3:
- raise ValueError('foo')
-
- return count
-
- assert retry_func(func=foo, attempts=5) == 3
- assert count == 3
diff --git a/pedantic/tests/test_with_decorated_methods.py b/pedantic/tests/test_with_decorated_methods.py
deleted file mode 100644
index 1f17e8e1..00000000
--- a/pedantic/tests/test_with_decorated_methods.py
+++ /dev/null
@@ -1,125 +0,0 @@
-import unittest
-from functools import wraps
-from typing import TypeVar, Generic, NoReturn
-
-from pedantic import DecoratorType, create_decorator, WithDecoratedMethods
-
-T = TypeVar('T')
-
-class Decorators(DecoratorType):
- FOO = '_foo'
- BAR = '_bar'
-
-
-foo = create_decorator(decorator_type=Decorators.FOO)
-bar = create_decorator(decorator_type=Decorators.BAR)
-
-
-class TestWithDecoratedMethods(unittest.TestCase):
- def test_no_decorated_methods(self):
- class MyClass(WithDecoratedMethods[Decorators]):
- pass
-
- instance = MyClass()
- assert instance.get_decorated_functions() == {Decorators.FOO: {}, Decorators.BAR: {}}
-
- def test_class_with_bad_property(self):
- class MyClass(WithDecoratedMethods[Decorators]):
- @property
- def bad(self) -> NoReturn:
- raise RuntimeError('bad man')
-
- instance = MyClass()
- assert instance.get_decorated_functions() == {Decorators.FOO: {}, Decorators.BAR: {}}
-
- def test_with_decorated_methods_sync(self):
- class MyClass(WithDecoratedMethods[Decorators]):
- @foo(42)
- def m1(self) -> None:
- print('bar')
-
- @foo(value=43)
- def m2(self) -> None:
- print('bar')
-
- @bar(value=44)
- def m3(self) -> None:
- print('bar')
-
- instance = MyClass()
- expected = {
- Decorators.FOO: {
- instance.m1: 42,
- instance.m2: 43,
- },
- Decorators.BAR: {
- instance.m3: 44,
- }
- }
- assert instance.get_decorated_functions() == expected
-
- def test_with_decorated_methods_async(self):
- class MyClass(WithDecoratedMethods[Decorators]):
- @foo(42)
- async def m1(self) -> None:
- print('bar')
-
- @foo(value=43)
- async def m2(self) -> None:
- print('bar')
-
- @bar(value=44)
- async def m3(self) -> None:
- print('bar')
-
- instance = MyClass()
- expected = {
- Decorators.FOO: {
- instance.m1: 42,
- instance.m2: 43,
- },
- Decorators.BAR: {
- instance.m3: 44,
- }
- }
- assert instance.get_decorated_functions() == expected
-
-
- def test_with_custom_transformation(self):
- def my_transformation(f, decorator_type, value):
- assert decorator_type == Decorators.BAR
- assert value == 42
-
- @wraps(f)
- def wrapper(*args, **kwargs):
- f(*args, **kwargs)
- return 4422 # we add a return value
-
- return wrapper
-
- my_decorator = create_decorator(decorator_type=Decorators.BAR, transformation=my_transformation)
-
- class MyClass(WithDecoratedMethods[Decorators]):
- @my_decorator(42)
- def m1(self) -> int:
- return 1
-
- instance = MyClass()
- expected = {
- Decorators.BAR: {
- instance.m1: 42,
- },
- Decorators.FOO: {},
- }
- assert instance.get_decorated_functions() == expected
-
- assert instance.m1() == 4422 # check that transformation was applied
-
- def test_with_decorated_methods_can_have_generic_child_class(self):
- class MyClass(Generic[T], WithDecoratedMethods[Decorators]):
- @foo(42)
- def m1(self) -> None: ...
-
- instance = MyClass[int]()
- actual = instance.get_decorated_functions()
- assert actual == {Decorators.FOO: {instance.m1: 42}, Decorators.BAR: {}}
\ No newline at end of file
diff --git a/pedantic/tests/tests_class_decorators.py b/pedantic/tests/tests_class_decorators.py
deleted file mode 100644
index 3a67ce13..00000000
--- a/pedantic/tests/tests_class_decorators.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import unittest
-
-# local file imports
-from pedantic.decorators.class_decorators import trace_class, timer_class
-
-
-class TestClassDecorators(unittest.TestCase):
-
- def test_trace_class(self):
- @trace_class
- class MyClass:
- def __init__(self, s: str) -> None:
- self.s = s
-
- def double(self, b: int) -> str:
- return self.s + str(b)
-
- @staticmethod
- def generator() -> 'MyClass':
- return MyClass(s='generated')
-
- m = MyClass.generator()
- m.double(b=42)
-
- def test_timer_class(self):
- @timer_class
- class MyClass:
- def __init__(self, s: str) -> None:
- self.s = s
-
- def double(self, b: int) -> str:
- return self.s + str(b)
-
- @staticmethod
- def generator() -> 'MyClass':
- return MyClass(s='generated')
-
- m = MyClass.generator()
- m.double(b=42)
diff --git a/pedantic/tests/tests_combination_of_decorators.py b/pedantic/tests/tests_combination_of_decorators.py
deleted file mode 100644
index 8a8ff775..00000000
--- a/pedantic/tests/tests_combination_of_decorators.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import unittest
-from abc import ABC, abstractmethod
-
-from pedantic.decorators.class_decorators import pedantic_class, for_all_methods
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.validators import Min
-from pedantic.exceptions import PedanticException, PedanticTypeCheckException, PedanticCallWithArgsException
-from pedantic.decorators.fn_deco_pedantic import pedantic
-from pedantic import overrides
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, Parameter, ReturnAs
-
-
-class TestCombinationOfDecorators(unittest.TestCase):
- def test_pedantic_overrides(self):
- class MyClass(ABC):
- @pedantic
- @abstractmethod
- def op(self, a: int) -> None:
- pass
-
- class Child(MyClass):
- a = 0
-
- @pedantic
- @overrides(MyClass)
- def op(self, a: int) -> None:
- self.a = a
-
- c = Child()
- c.op(a=42)
-
- def test_pedantic_below_validate(self):
- @validate(
- Parameter(name='x', validators=[Min(0)]),
- return_as=ReturnAs.KWARGS_WITH_NONE,
- )
- @pedantic
- def some_calculation(x: int) -> int:
- return x
-
- some_calculation(x=42)
- some_calculation(42)
-
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(x=-1)
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(x=-42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_calculation(x=1.0)
-
- def test_pedantic_above_validate(self):
- @pedantic
- @validate(
- Parameter(name='x', validators=[Min(0)]),
- )
- def some_calculation(x: int) -> int:
- return x
-
- some_calculation(x=42)
-
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(x=-1)
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(x=-42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_calculation(x=1.0)
- with self.assertRaises(expected_exception=PedanticException):
- some_calculation(42)
-
- def test_pedantic_above_validate_on_instance_method(self):
- class MyClass:
- @pedantic
- @validate(
- Parameter(name='x', validators=[Min(0)]),
- )
- def some_calculation(self, x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-1)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_calculation(x=1.0)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.some_calculation(42)
-
- def test_pedantic_below_validate_on_instance_method(self):
- class MyClass:
- @validate(
- Parameter(name='x', validators=[Min(0)]),
- )
- @pedantic
- def some_calculation(self, x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- m.some_calculation(42)
-
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-1)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_calculation(x=1.0)
-
- def test_pedantic_class_with_validate_instance_method(self):
- @pedantic_class
- class MyClass:
- @validate(
- Parameter(name='x', validators=[Min(0)]),
- )
- def some_calculation(self, x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-1)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(x=-42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_calculation(x=1.0)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.some_calculation(42)
-
- def test_pedantic_class_static_method_1(self):
- @pedantic_class
- class MyClass:
- @staticmethod
- def some_calculation(x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- MyClass.some_calculation(x=45)
-
- def test_pedantic_class_static_method_2(self):
- """Never do this, but it works"""
- @for_all_methods(staticmethod)
- @pedantic_class
- class MyClass:
- def some_calculation(x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_calculation(x=42.0)
- MyClass.some_calculation(x=45)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.some_calculation(x=45.0)
-
- def test_pedantic_static_method_1(self):
- class MyClass:
- @staticmethod
- @pedantic
- def some_calculation(x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(x=42)
- MyClass.some_calculation(x=45)
diff --git a/pedantic/tests/tests_decorated_function.py b/pedantic/tests/tests_decorated_function.py
deleted file mode 100644
index e5266e79..00000000
--- a/pedantic/tests/tests_decorated_function.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import unittest
-
-from pedantic.models.decorated_function import DecoratedFunction
-
-
-class TestDecoratedFunction(unittest.TestCase):
- def test_static_method(self):
- def f_1(): pass
-
- deco_f = DecoratedFunction(f_1)
- self.assertFalse(deco_f.is_static_method)
-
- class MyClass:
- def f_1(self): pass
-
- @staticmethod
- def f_2(): pass
-
- @classmethod
- def f_3(cls): pass
-
- deco_f_1 = DecoratedFunction(MyClass.f_1)
- deco_f_2 = DecoratedFunction(MyClass.f_2)
- deco_f_3 = DecoratedFunction(MyClass.f_3)
-
- self.assertFalse(deco_f_1.is_static_method)
- self.assertTrue(deco_f_2.is_static_method)
- self.assertFalse(deco_f_3.is_static_method)
-
- def test_function_wants_args(self):
- def f_1(*args, **kwargs): pass
-
- def f_2(a, b, *args, **kwargs): pass
-
- def f_3(a, b, *args): pass
-
- def f_4(*args): pass
-
- def f_5(): pass
-
- self.assertTrue(DecoratedFunction(f_1).wants_args)
- self.assertTrue(DecoratedFunction(f_2).wants_args)
- self.assertTrue(DecoratedFunction(f_3).wants_args)
- self.assertTrue(DecoratedFunction(f_4).wants_args)
- self.assertFalse(DecoratedFunction(f_5).wants_args)
-
- class MyClass:
- def f(self): pass
-
- @staticmethod
- def g(): pass
-
- self.assertFalse(DecoratedFunction(MyClass.f).wants_args)
- self.assertFalse(DecoratedFunction(MyClass.g).wants_args)
-
- def test_is_property_setter(self):
- def f_1(): pass
-
- self.assertFalse(DecoratedFunction(f_1).is_property_setter)
-
- class MyClass:
- _h = 42
-
- def f_1(self): pass
-
- @staticmethod
- def f_2(): pass
-
- self.assertFalse(DecoratedFunction(MyClass.f_1).is_property_setter)
- self.assertFalse(DecoratedFunction(MyClass.f_2).is_property_setter)
-
- def test_wants_kwargs(self):
- def f_1(*args, **kwargs): pass
-
- def f_2(a, b, *args, **kwargs): pass
-
- def f_3(a, b, *args): pass
-
- def f_4(*args): pass
-
- def f_5(): pass
-
- def f_6(a, b, c): pass
-
- self.assertFalse(DecoratedFunction(f_1).should_have_kwargs)
- self.assertFalse(DecoratedFunction(f_2).should_have_kwargs)
- self.assertFalse(DecoratedFunction(f_3).should_have_kwargs)
- self.assertFalse(DecoratedFunction(f_4).should_have_kwargs)
- self.assertTrue(DecoratedFunction(f_5).should_have_kwargs)
- self.assertTrue(DecoratedFunction(f_6).should_have_kwargs)
-
- class A:
- def f(self): pass
-
- @staticmethod
- def g(): pass
-
- def __compare__(self, other): pass
-
- self.assertTrue(DecoratedFunction(A.f).should_have_kwargs)
- self.assertTrue(DecoratedFunction(A.g).should_have_kwargs)
- self.assertFalse(DecoratedFunction(A.__compare__).should_have_kwargs)
-
- def test_instance_method(self):
- def h(): pass
-
- self.assertFalse(DecoratedFunction(h).is_instance_method)
-
- class A:
- def f(self): pass
-
- @staticmethod
- def g(): pass
-
- self.assertTrue(DecoratedFunction(A.f).is_instance_method)
- self.assertFalse(DecoratedFunction(A.g).is_instance_method)
-
- def test_num_decorators(self):
- def decorator(f):
- return f
-
- def f_1(): pass
-
- @decorator
- def f_2(): pass
-
- @decorator
- @decorator
- def f_3(): pass
-
- @decorator
- @decorator
- @decorator
- def f_4():
- pass
-
- self.assertEqual(DecoratedFunction(f_1).num_of_decorators, 0)
- self.assertEqual(DecoratedFunction(f_2).num_of_decorators, 1)
- self.assertEqual(DecoratedFunction(f_3).num_of_decorators, 2)
- self.assertEqual(DecoratedFunction(f_4).num_of_decorators, 3)
diff --git a/pedantic/tests/tests_docstring.py b/pedantic/tests/tests_docstring.py
deleted file mode 100644
index ca89a7bb..00000000
--- a/pedantic/tests/tests_docstring.py
+++ /dev/null
@@ -1,481 +0,0 @@
-from unittest import TestCase
-from typing import List, Optional
-
-from pedantic.exceptions import PedanticTypeCheckException, PedanticDocstringException
-from pedantic.decorators.fn_deco_pedantic import pedantic_require_docstring, pedantic
-from pedantic.decorators.class_decorators import pedantic_class_require_docstring, pedantic_class
-
-
-class TestRequireDocstringGoogleFormat(TestCase):
-
- def test_no_docstring(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- def test_one_line_doc_string_missing_arguments_and_return(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(n: int, m: int, i: int) -> int:
- """Returns the sum of the three args."""
- return n + m + i
-
- def test_one_line_doc_string_corrected(self):
- @pedantic_require_docstring
- def calc(n: int, m: int, i: int) -> int:
- """Returns the sum of the three args.
-
- Args:
- n (int): something
- m (int): something
- i (int): something
-
- Returns:
- int: bar
- """
- return n + m + i
-
- calc(n=42, m=40, i=38)
-
- def test_list_vs_typing_list(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- list: a list of strings representing the header columns
- """
- return [file_loc, str(print_cols)]
-
- def test_google_docstring_2(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
-
- return [file_loc, str(print_cols)]
-
- calc(file_loc='Hi', print_cols=False)
-
- def test_google_docstring_3(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
-
- return [file_loc, str(print_cols)]
-
- calc(file_loc='Hi', print_cols=False)
-
- def test_more_parameter_documented_than_the_function_takes(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
- amount (int): THIS ARGUMENT IS NOT TAKEN BY THE FUNCTION
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
- return [file_loc, str(print_cols)]
-
- def test_google_docstring_corrected(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool, amount: int) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
- amount (int): now it is
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
-
- return [file_loc, str(print_cols), str(amount)]
-
- calc(file_loc='Hi', print_cols=False, amount=42)
-
- def test_no_args_keyword_before_documented_arguments(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> list:
- """Gets and prints the spreadsheet's header columns
-
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- list: a list of strings representing the header columns
- """
- return [file_loc, str(print_cols)]
-
- def test_google_no_return_keyword(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> list:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- list: a list of strings representing the header columns
- """
- return [file_loc, str(print_cols)]
-
- def test_keep_it_simple(self):
- @pedantic_require_docstring
- def calc() -> None:
- """Gets and prints the spreadsheet's header columns"""
- pass
-
- calc()
-
- def test_docstring_misses_argument(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(name: str) -> None:
- """Gets and prints the spreadsheet's header columns"""
- print('hi ' + name)
-
- def test_keep_it_simple_2_corrected(self):
- @pedantic_require_docstring
- def calc(name: str) -> None:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- name (str): the name
- """
- print('hi ' + name)
-
- calc(name='maria')
-
- def test_undocumented_arg(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool, number: int) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
- return [file_loc, str(print_cols), str(number)]
-
- def test_undocumented_arg_corrected(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool, number: int) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
- number (int): magic number
-
- Returns:
- List[str]: a list of strings representing the header columns
- """
-
- return [file_loc, str(print_cols), str(number)]
-
- calc(file_loc='Hi', print_cols=False, number=42)
-
- def test_restructured_text_style_doctsring_cannot_be_parsed_yet(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- :param file_loc: The file location of the spreadsheet
- :type file_loc: str
- :param print_cols: A flag used to print the columns to the console
- :type print_cols: bool
- :returns: a list of strings representing the header column
- :rtype: List[str]
- """
- return [file_loc, str(print_cols)]
-
- def test_return_nothing_but_document_return_value(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool):
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- list: a list of strings representing the header columns
- """
- print([file_loc, str(print_cols)])
-
- def test_return_nothing_but_document_return_value_2(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> None:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- list: a list of strings representing the header columns
- """
- print([file_loc, str(print_cols)])
-
- def test_return_value_1_corrected(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> None:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
- """
-
- a = [file_loc, str(print_cols)]
- calc(file_loc='Hi', print_cols=False)
-
- def test_return_value_is_not_documented(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- # the error message here is actually wrong due to the behavior of the docstring-parser package
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console (default is False)
-
- Returns:
- """
- return [file_loc, str(print_cols)]
-
- def test_return_value_2_corrected(self):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
-
- Returns:
- List[str]: results
- """
-
- return [file_loc, str(print_cols)]
-
- calc(file_loc='Hi', print_cols=False)
-
- def test_return_value_is_not_documented_3(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc(file_loc: str, print_cols: bool) -> List[str]:
- """Gets and prints the spreadsheet's header columns
-
- Args:
- file_loc (str): The file location of the spreadsheet
- print_cols (bool): A flag used to print the columns to the console
- (default is False)
- """
- return [file_loc, str(print_cols)]
-
- def test_wrong_format_1(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- class MyText:
- text = 'hi'
-
- @pedantic_require_docstring
- def __contains__(self, substring: str) -> bool:
- """
- Checks if contains substring.
- Overriding __contains__ build in functions allows to use the 'in' operator blah readability
-
- Example:
- my_text = MyText('abc')
- if 'ab' in my_text -> true
- :param: substring: substring
- :return: True if substring is stored, False otherwise.
- """
- return substring in self.text
-
- def test_undocumented_arg_3(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic
- def calc(a: int, b: float, c: str) -> str:
- """Returns some cool string
-
- Args:
- a (int): something
- b (float): something
-
- Returns:
- str: something
- """
- return str(a) + str(b) + c
-
- calc(a=42, b=3.14, c='hi')
-
- def test_pedantic_1_corrected(self):
- @pedantic
- def calc(a: int, b: float, c: str) -> str:
- """Returns some cool string
-
- Args:
- a (int): something
- b (float): something
- c (str): something
-
- Returns:
- str: something
- """
- return str(a) + str(b) + c
-
- calc(a=42, b=3.14, c='hi')
-
- def test_documented_none_as_return_type(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def calc() -> None:
- """some cool stuff
-
- Returns:
- None: the evil void
- """
- pass
-
- def test_exception_in_docstring_parser(self):
- @pedantic_class
- class Foo:
- def func(self, b: str) -> str:
- """
- Function with docstring syntax error below.
- Args:
- b (str):
- simple string
- Returns:
- str: simple string
- """
- return b
-
- def test_user_class(self):
- class BPMNEnum:
- attr = 'BPMNEnum'
-
- class BPMNElement:
- attr = 'BPMNElement'
-
- @pedantic_class_require_docstring
- class MyClass:
- def make_element(self, element_type: BPMNEnum,
- src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
- """
- Searches all element_types in XML-DOM and returns corresponding
- BPMN-Objects.
- Args:
- element_type(BPMNEnum): abc
- src_tgt_elements (Optional[List[BPMNElement]]): abc
-
- Returns:
- List[BPMNElement]: abc
- """
- element_type.attr = '42'
- return src_tgt_elements
-
- m = MyClass()
- m.make_element(element_type=BPMNEnum(), src_tgt_elements=[BPMNElement()])
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.make_element(element_type=BPMNElement(), src_tgt_elements=[BPMNEnum()])
-
- def test_user_class_with_typing(self):
- class BPMNEnum:
- attr = 'BPMNEnum'
-
- class BPMNElement:
- attr = 'BPMNElement'
-
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_class_require_docstring
- class MyClass:
-
- def make_element(self, element_type: BPMNEnum,
- src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
- """
- Searches all element_types in XML-DOM and returns corresponding
- BPMN-Objects.
- Args:
- element_type(BPMNEnum): abc
- src_tgt_elements (typing.Optional[List[BPMNElement]]): abc
-
- Returns:
- List[BPMNElement]: abc
- """
- element_type.attr = '42'
- return src_tgt_elements
-
- def test_factory(self):
- @pedantic_require_docstring
- def get_instance() -> TestCase:
- """
- Returns:
- TestCase: A new TestCase
- """
- return TestCase()
-
- get_instance()
-
- def test_pedantic_args(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic(require_docstring=True)
- def no_docstrings() -> None:
- print('.')
-
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_require_docstring
- def no_docstrings() -> None:
- print('.')
diff --git a/pedantic/tests/tests_doctests.py b/pedantic/tests/tests_doctests.py
deleted file mode 100644
index 193311f5..00000000
--- a/pedantic/tests/tests_doctests.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import unittest
-import doctest
-
-
-def run_doctests() -> None:
- unittest.TextTestRunner().run(get_doctest_test_suite())
-
-
-def get_doctest_test_suite() -> unittest.TestSuite:
- parent_module = __import__('pedantic')
- modules = [
- parent_module.decorators.fn_deco_count_calls,
- parent_module.decorators.fn_deco_deprecated,
- parent_module.decorators.fn_deco_does_same_as_function,
- parent_module.decorators.fn_deco_in_subprocess,
- parent_module.decorators.fn_deco_overrides,
- parent_module.decorators.fn_deco_pedantic,
- parent_module.decorators.fn_deco_rename_kwargs,
- parent_module.decorators.fn_deco_timer,
- parent_module.decorators.fn_deco_trace,
- parent_module.decorators.fn_deco_trace_if_returns,
- parent_module.decorators.fn_deco_unimplemented,
- parent_module.mixins.generic_mixin,
- parent_module.type_checking_logic.check_types,
- parent_module.type_checking_logic.check_generic_classes,
- parent_module.type_checking_logic.check_docstring,
- parent_module.decorators.cls_deco_frozen_dataclass,
- ]
- test_suites = [doctest.DocTestSuite(module=module, optionflags=doctest.ELLIPSIS) for module in modules]
- return unittest.TestSuite(test_suites)
-
-
-if __name__ == '__main__':
- run_doctests()
diff --git a/pedantic/tests/tests_environment_variables.py b/pedantic/tests/tests_environment_variables.py
deleted file mode 100644
index 1405260e..00000000
--- a/pedantic/tests/tests_environment_variables.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import unittest
-
-from pedantic.exceptions import PedanticTypeCheckException
-from pedantic.env_var_logic import enable_pedantic, disable_pedantic, is_enabled
-from pedantic.decorators.fn_deco_pedantic import pedantic
-
-
-class TestEnvironmentVariables(unittest.TestCase):
- def setUp(self) -> None:
- self.state = is_enabled()
- enable_pedantic()
-
- def tearDown(self) -> None:
- enable_pedantic()
-
- def test_pedantic_enabled(self):
- enable_pedantic()
-
- @pedantic
- def some_method():
- return 42
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_method()
-
- def test_pedantic_disabled(self):
- disable_pedantic()
-
- @pedantic
- def some_method():
- return 42
-
- some_method()
-
- def test_enable_disable(self):
- enable_pedantic()
- self.assertTrue(is_enabled())
- disable_pedantic()
- self.assertFalse(is_enabled())
- enable_pedantic()
- self.assertTrue(is_enabled())
diff --git a/pedantic/tests/tests_generator.py b/pedantic/tests/tests_generator.py
deleted file mode 100644
index 0f016c6b..00000000
--- a/pedantic/tests/tests_generator.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import unittest
-from typing import Generator, Iterator, Iterable, List
-
-from pedantic.exceptions import PedanticTypeCheckException
-from pedantic.decorators.fn_deco_pedantic import pedantic
-
-
-class TestGenerator(unittest.TestCase):
- def test_iterator(self):
- @pedantic
- def gen_func() -> Iterator[int]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- gen = gen_func()
- next(gen)
-
- def test_iterator_wrong_type_hint(self):
- @pedantic
- def genfunc() -> Iterator[float]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- gen = genfunc()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- next(gen)
-
- def test_iterator_no_type_args(self):
- @pedantic
- def genfunc() -> Iterator:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- genfunc()
-
- def test_iterator_completely_wrong_type_hint(self):
- @pedantic
- def gen_func() -> List[int]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- gen_func()
-
- def test_iterable(self):
- @pedantic
- def gen_func() -> Iterable[int]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- gen = gen_func()
- next(gen)
-
- def test_iterable_no_type_args(self):
- @pedantic
- def gen_func() -> Iterable:
- num = 0
-
- while num < 100:
- yield num
- num += 1
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- gen_func()
-
- def test_generator(self):
- @pedantic
- def gen_func() -> Generator[int, None, str]:
- num = 0
-
- while num < 100:
- yield num
- num += 1
- return 'Done'
-
- gen = gen_func()
- next(gen)
-
- def test_invalid_no_type_args_generator(self):
- @pedantic
- def gen_func() -> Generator:
- num = 0
-
- while num < 100:
- yield num
- num += 1
- return 'Done'
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- gen_func()
diff --git a/pedantic/tests/tests_generic_classes.py b/pedantic/tests/tests_generic_classes.py
deleted file mode 100644
index 1263eeeb..00000000
--- a/pedantic/tests/tests_generic_classes.py
+++ /dev/null
@@ -1,317 +0,0 @@
-import unittest
-from typing import Generic, TypeVar, Any, List, Optional, Union
-
-from pedantic.constants import TYPE_VAR_METHOD_NAME, TYPE_VAR_SELF
-from pedantic.decorators.class_decorators import pedantic_class
-from pedantic.exceptions import PedanticTypeVarMismatchException
-
-
-class TestGenericClasses(unittest.TestCase):
- def test_pedantic_generic_class(self):
- T = TypeVar('T')
-
- @pedantic_class
- class LoggedVar(Generic[T]):
- def __init__(self, value: T, name: str, logger: Any) -> None:
- self.name = name
- self.logger = logger
- self.value = value
-
- def set(self, new: T) -> None:
- self.log(message='Set ' + repr(self.value))
- self.value = new
-
- def get(self) -> T:
- self.log(message='Get ' + repr(self.value))
- return self.value
-
- def log(self, message: str) -> None:
- self.logger = self.name + message
-
- o = LoggedVar[int](value=42, name='hi', logger='test')
- o.set(new=57)
- self.assertTrue(isinstance(o.get(), int))
-
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- o.set(new=3.14)
-
- def test_stack(self):
- T = TypeVar('T')
-
- @pedantic_class
- class Stack(Generic[T]):
- def __init__(self) -> None:
- self.items: List[T] = []
-
- def push(self, item: T) -> None:
- self.items.append(item)
-
- def pop(self) -> T:
- return self.items.pop()
-
- def empty(self) -> bool:
- return not self.items
-
- def top(self) -> Optional[T]:
- if len(self.items) > 0:
- return self.items[len(self.items) - 1]
- else:
- return None
-
- my_stack = Stack[str]()
- get_type_vars = getattr(my_stack, TYPE_VAR_METHOD_NAME)
- self.assertEqual(get_type_vars(), {T: str, TYPE_VAR_SELF: Stack})
- with self.assertRaises(expected_exception=IndexError):
- my_stack.pop()
- self.assertIsNone(my_stack.top())
- self.assertIsNone(my_stack.top())
- # self.assertFalse(T in get_type_vars())
- my_stack.push(item='hi')
- self.assertTrue(T in get_type_vars())
- my_stack.push(item='world')
- self.assertTrue(T in get_type_vars())
- self.assertTrue(len(get_type_vars()), 1)
- self.assertEqual(my_stack.pop(), 'world')
- self.assertEqual(my_stack.pop(), 'hi')
- self.assertIsNone(my_stack.top())
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- my_stack.push(item=42)
-
- my_other_stack = Stack[int]()
- get_type_vars = getattr(my_other_stack, TYPE_VAR_METHOD_NAME)
- self.assertEqual(get_type_vars(), {T: int, TYPE_VAR_SELF: Stack})
- with self.assertRaises(expected_exception=IndexError):
- my_other_stack.pop()
- self.assertIsNone(my_other_stack.top())
- self.assertIsNone(my_other_stack.top())
- my_other_stack.push(item=100)
- self.assertTrue(len(get_type_vars()), 1)
- my_other_stack.push(item=142)
- self.assertTrue(len(get_type_vars()), 1)
- self.assertEqual(my_other_stack.pop(), 142)
- self.assertEqual(my_other_stack.pop(), 100)
- self.assertIsNone(my_other_stack.top())
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- my_other_stack.push(item='42')
-
- def test_generic_class_initialised_without_generics(self):
- T = TypeVar('T')
-
- @pedantic_class
- class MyClass(Generic[T]):
- def __init__(self, a: T) -> None:
- self.a = a
-
- def get_a(self) -> T:
- return self.a
-
- def set_a(self, val: T) -> None:
- self.a = val
-
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- m = MyClass(a=42)
-
- def test_generic_class_initialised_without_generics_2(self):
- T = TypeVar('T')
-
- @pedantic_class
- class MyClass(Generic[T]):
- def __init__(self, a: T) -> None:
- self.a = a
-
- def get_a(self) -> T:
- return self.a
-
- def set_a(self, val: T) -> None:
- self.a = val
-
- MyClass(a=42) # it is not recognized if it isn't assigned
-
- def test_generic_class_inheritance(self):
- class Parent:
- pass
-
- class Child1(Parent):
- pass
-
- class Child2(Parent):
- pass
-
- T = TypeVar('T')
-
- @pedantic_class
- class MyClass(Generic[T]):
- def __init__(self, a: T) -> None:
- self.a = a
-
- def get_a(self) -> T:
- return self.a
-
- def set_a(self, val: T) -> None:
- self.a = val
-
- m = MyClass[Parent](a=Child1())
- self.assertTrue(isinstance(m.get_a(), Child1))
- self.assertFalse(isinstance(m.get_a(), Child2))
- m.set_a(val=Child2())
- self.assertTrue(isinstance(m.get_a(), Child2))
- self.assertFalse(isinstance(m.get_a(), Child1))
-
- def test_merge_dicts(self):
- def create():
- T = TypeVar('T')
-
- @pedantic_class
- class MyClass(Generic[T]):
- def __init__(self, a: T) -> None:
- self.a = a
-
- def get_a(self) -> T:
- return self.a
-
- def set_a(self, val: T) -> None:
- self.a = val
- return MyClass(a=42)
- a = create()
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- a.set_a(val='hi')
-
- def test_recursion_depth_exceeded(self):
- T = TypeVar('T')
-
- @pedantic_class
- class Stack(Generic[T]):
- def __init__(self) -> None:
- self.items: List[T] = []
-
- def len(self) -> int:
- return len(self.items)
-
- def push(self, item: T) -> None:
- self.items.append(item)
-
- def pop(self) -> T:
- if len(self.items) > 0:
- return self.items.pop()
- else:
- raise ValueError()
-
- def empty(self) -> bool:
- return not self.items
-
- def top(self) -> Optional[T]:
- if len(self.items) > 0:
- return self.items[len(self.items) - 1]
- else:
- return None
-
- def __len__(self) -> int:
- return len(self.items)
-
- def create_stack():
- stack = Stack[int]()
- return stack
-
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- stack: Stack[int] = Stack()
- self.assertTrue(stack.empty())
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- stack = Stack()
- self.assertTrue(stack.empty())
- stack = create_stack()
- self.assertTrue(stack.empty())
-
- def test_generic_union(self):
- T = TypeVar('T')
-
- @pedantic_class
- class Stack(Generic[T]):
- def __init__(self) -> None:
- self.items: List[T] = []
-
- def len(self) -> int:
- return len(self.items)
-
- def push(self, item: T) -> None:
- self.items.append(item)
-
- def pop(self) -> T:
- if len(self.items) > 0:
- return self.items.pop()
- else:
- raise ValueError()
-
- def empty(self) -> bool:
- return not self.items
-
- def top(self) -> Optional[T]:
- if len(self.items) > 0:
- return self.items[len(self.items) - 1]
- else:
- return None
-
- def __len__(self) -> int:
- return len(self.items)
-
- s = Stack[Union[int, float, str]]()
- s.push(item=42)
- s.push(item='hello')
- s.push(item=3.1415)
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- s.push(item=[1, 2])
-
- def test_inheritance(self):
- T = TypeVar('T')
-
- @pedantic_class
- class Stack(Generic[T]):
- def __init__(self) -> None:
- self.items: List[T] = []
-
- def len(self) -> int:
- return len(self.items)
-
- def push(self, item: T) -> None:
- self.items.append(item)
-
- def pop(self) -> T:
- if len(self.items) > 0:
- return self.items.pop()
- else:
- raise ValueError()
-
- def empty(self) -> bool:
- return not self.items
-
- def top(self) -> Optional[T]:
- if len(self.items) > 0:
- return self.items[len(self.items) - 1]
- else:
- return None
-
- def __len__(self) -> int:
- return len(self.items)
-
- @pedantic_class
- class Parent:
- pass
-
- @pedantic_class
- class Child1(Parent):
- pass
-
- @pedantic_class
- class Child2(Parent):
- pass
-
- parent_stack = Stack[Parent]()
- parent_stack.push(item=Child1())
- parent_stack.push(item=Child2())
- parent_stack.push(item=Parent())
-
- child_1_stack = Stack[Child1]()
- child_1_stack.push(item=Child1())
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- child_1_stack.push(item=Child2())
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- child_1_stack.push(item=Parent())
diff --git a/pedantic/tests/tests_main.py b/pedantic/tests/tests_main.py
deleted file mode 100644
index d8f0be9f..00000000
--- a/pedantic/tests/tests_main.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import unittest
-import sys
-import os
-
-sys.path.append(os.getcwd())
-
-from pedantic.tests.test_retry import TestRetry, TestRetryFunc
-from pedantic.tests.test_with_decorated_methods import TestWithDecoratedMethods
-from pedantic.tests.validate.test_convert_value import TestConvertValue
-from pedantic.tests.test_rename_kwargs import TestRenameKwargs
-from pedantic.tests.validate.test_datetime_isoformat import TestValidatorDatetimeIsoformat
-from pedantic.tests.validate.test_flask_parameters import TestFlaskParameters
-from pedantic.tests.validate.test_parameter_environment_variable import TestParameterEnvironmentVariable
-from pedantic.tests.validate.test_validate import TestValidate
-from pedantic.tests.validate.test_validate import AsyncValidateTests
-from pedantic.tests.tests_small_method_decorators import AsyncSmallDecoratorTests
-from pedantic.tests.tests_pedantic_async import TestPedanticAsyncio
-from pedantic.tests.test_in_subprocess import TestInSubprocess
-from pedantic.tests.test_async_context_manager import TestAsyncContextManager
-from pedantic.tests.tests_pedantic_python_311 import TestPedanticPython311AddedStuff
-from pedantic.tests.test_resolve_forward_ref import TestResolveForwardRef
-from pedantic.tests.test_generic_mixin import TestGenericMixin
-from pedantic.tests.test_assert_value_matches_type import TestAssertValueMatchesType
-from pedantic.tests.validate.test_validator_composite import TestValidatorComposite
-from pedantic.tests.validate.test_validator_datetime_unix_timestamp import TestValidatorDatetimeUnixTimestamp
-from pedantic.tests.validate.test_validator_email import TestValidatorEmail
-from pedantic.tests.validate.test_validator_for_each import TestValidatorForEach
-from pedantic.tests.validate.test_validator_is_enum import TestValidatorIsEnum
-from pedantic.tests.validate.test_validator_is_uuid import TestValidatorIsUUID
-from pedantic.tests.validate.test_validator_match_pattern import TestValidatorMatchPattern
-from pedantic.tests.validate.test_validator_max import TestValidatorMax
-from pedantic.tests.validate.test_validator_max_length import TestValidatorMaxLength
-from pedantic.tests.validate.test_validator_min import TestValidatorMin
-from pedantic.tests.validate.test_validator_min_length import TestValidatorMinLength
-from pedantic.tests.validate.test_validator_not_empty import TestValidatorNotEmpty
-from pedantic.tests.test_generator_wrapper import TestGeneratorWrapper
-from pedantic.tests.tests_mock import TestMock
-from pedantic.tests.tests_doctests import get_doctest_test_suite
-from pedantic.tests.test_frozen_dataclass import TestFrozenDataclass
-from pedantic.tests.tests_require_kwargs import TestRequireKwargs
-from pedantic.tests.tests_class_decorators import TestClassDecorators
-from pedantic.tests.tests_pedantic_class import TestPedanticClass
-from pedantic.tests.tests_pedantic import TestDecoratorRequireKwargsAndTypeCheck
-from pedantic.tests.tests_small_method_decorators import TestSmallDecoratorMethods
-from pedantic.tests.tests_combination_of_decorators import TestCombinationOfDecorators
-from pedantic.tests.tests_docstring import TestRequireDocstringGoogleFormat
-from pedantic.tests.tests_pedantic_class_docstring import TestPedanticClassDocstring
-from pedantic.tests.tests_decorated_function import TestDecoratedFunction
-from pedantic.tests.tests_environment_variables import TestEnvironmentVariables
-from pedantic.tests.tests_generic_classes import TestGenericClasses
-from pedantic.tests.tests_generator import TestGenerator
-from pedantic.tests.test_context_manager import TestContextManager
-
-
-def run_all_tests() -> None:
- test_classes_to_run = [
- TestAssertValueMatchesType,
- TestGenericMixin,
- TestWithDecoratedMethods,
- TestRequireKwargs,
- TestClassDecorators,
- TestContextManager,
- TestFrozenDataclass,
- TestPedanticClass,
- TestDecoratorRequireKwargsAndTypeCheck,
- TestSmallDecoratorMethods,
- TestCombinationOfDecorators,
- TestRequireDocstringGoogleFormat,
- TestPedanticClassDocstring,
- TestDecoratedFunction,
- TestEnvironmentVariables,
- TestGenericClasses,
- TestGenerator,
- TestMock,
- TestGeneratorWrapper,
- TestRenameKwargs,
- TestRetry,
- TestRetryFunc,
- TestResolveForwardRef,
- # validate
- TestValidatorDatetimeIsoformat,
- TestFlaskParameters,
- TestParameterEnvironmentVariable,
- TestConvertValue,
- TestValidate,
- TestValidatorComposite,
- TestValidatorDatetimeUnixTimestamp,
- TestValidatorEmail,
- TestValidatorForEach,
- TestValidatorIsEnum,
- TestValidatorIsUUID,
- TestValidatorMatchPattern,
- TestValidatorMax,
- TestValidatorMaxLength,
- TestValidatorMin,
- TestValidatorMinLength,
- TestValidatorNotEmpty,
-
- # async
- AsyncValidateTests,
- AsyncSmallDecoratorTests,
- TestPedanticAsyncio,
- TestInSubprocess,
- TestAsyncContextManager,
-
- TestPedanticPython311AddedStuff,
- ]
-
- loader = unittest.TestLoader()
- suites_list = [get_doctest_test_suite()]
-
- for test_class in test_classes_to_run:
- suite = loader.loadTestsFromTestCase(test_class)
- suites_list.append(suite)
-
- big_suite = unittest.TestSuite(suites_list)
- runner = unittest.TextTestRunner()
- result = runner.run(big_suite)
- assert not result.errors and not result.failures, f'Some tests failed!'
-
-
-if __name__ == '__main__':
- run_all_tests()
diff --git a/pedantic/tests/tests_mock.py b/pedantic/tests/tests_mock.py
deleted file mode 100644
index dcbe24b7..00000000
--- a/pedantic/tests/tests_mock.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from unittest import TestCase
-
-from pedantic import mock
-
-
-class TestMock(TestCase):
- def test_mock(self) -> None:
- @mock(return_value=42)
- def my_function(a, b, c):
- return a + b + c
-
- assert my_function(1, 2, 3) == 42
- assert my_function(100, 200, 300) == 42
diff --git a/pedantic/tests/tests_pedantic.py b/pedantic/tests/tests_pedantic.py
deleted file mode 100644
index fc0d6925..00000000
--- a/pedantic/tests/tests_pedantic.py
+++ /dev/null
@@ -1,2462 +0,0 @@
-import os.path
-import types
-import typing
-import unittest
-from dataclasses import dataclass
-from datetime import datetime, date
-from functools import wraps, partial
-from io import BytesIO, StringIO
-from typing import List, Tuple, Callable, Any, Optional, Union, Dict, Set, FrozenSet, NewType, TypeVar, Sequence, \
- AbstractSet, Iterator, NamedTuple, Collection, Type, Generator, Generic, BinaryIO, TextIO, Iterable, Container, \
- NoReturn, ClassVar, Literal
-from enum import Enum, IntEnum
-
-from pedantic import pedantic_class
-from pedantic.exceptions import PedanticTypeCheckException, PedanticException, PedanticCallWithArgsException, \
- PedanticTypeVarMismatchException
-from pedantic.decorators.fn_deco_pedantic import pedantic
-
-TEST_FILE = 'test.txt'
-
-
-class Parent:
- pass
-
-
-class Child(Parent):
- def method(self, a: int):
- pass
-
-
-class TestDecoratorRequireKwargsAndTypeCheck(unittest.TestCase):
- def tearDown(self) -> None:
- if os.path.isfile(TEST_FILE):
- os.remove(TEST_FILE)
-
- def test_no_kwargs(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- calc(42, 40, 38)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- calc(42, m=40, i=38)
- calc(n=42, m=40, i=38)
-
- def test_nested_type_hints_1(self):
- @pedantic
- def calc(n: int) -> List[List[float]]:
- return [0.0 * n]
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)
-
- def test_nested_type_hints_1_corrected(self):
- @pedantic
- def calc(n: int) -> List[List[float]]:
- return [[0.0 * n]]
-
- calc(n=42)
-
- def test_nested_type_hints_2(self):
- """Problem here: int != float"""
- @pedantic
- def calc(n: int) -> List[Tuple[float, str]]:
- return [(n, str(n))]
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)
-
- def test_nested_type_hints_2_corrected(self):
- @pedantic
- def calc(n: int) -> List[Tuple[int, str]]:
- return [(n, str(n))]
-
- @pedantic
- def calc_2(n: float) -> List[Tuple[float, str]]:
- return [(n, str(n))]
-
- calc(n=42)
- calc_2(n=42.0)
-
- def test_nested_type_hints_3(self):
- """Problem here: inner function actually returns Tuple[int, str]"""
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[float, str]:
- return n * x, str(y)
- return f
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)(x=3, y=3.14)
-
- def test_nested_type_hints_3_corrected(self):
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[int, str]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[int, str]:
- return n * x, str(y)
-
- return f
-
- calc(n=42)(x=3, y=3.14)
-
- def test_nested_type_hints_4(self):
- """Problem here: return type is actually float"""
- @pedantic
- def calc(n: List[List[float]]) -> int:
- return n[0][0]
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=[[42.0]])
-
- def test_nested_type_hints_corrected(self):
- @pedantic
- def calc(n: List[List[float]]) -> int:
- return int(n[0][0])
-
- calc(n=[[42.0]])
-
- def test_nested_type_hints_5(self):
- """Problem here: Tuple[float, str] != Tuple[float, float]"""
-
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[float, float]:
- return n * float(x), y
- return f
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)
-
- def test_nested_type_hints_5_corrected(self):
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[float, float]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[float, float]:
- return n * float(x), y
- return f
-
- calc(n=42)
-
- def test_missing_type_hint_1(self):
- """Problem here: type hint for n missed"""
- @pedantic
- def calc(n) -> float:
- return 42.0 * n
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)
-
- def test_missing_type_hint_1_corrected(self):
- @pedantic
- def calc(n: int) -> float:
- return 42.0 * n
-
- calc(n=42)
-
- def test_missing_type_hint_2(self):
- """Problem here: Return type annotation missed"""
- @pedantic
- def calc(n: int):
- return 'Hi' + str(n)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42)
-
- def test_missing_type_hint_2_corrected(self):
- @pedantic
- def calc(n: int) -> str:
- return 'Hi' + str(n)
-
- calc(n=42)
-
- def test_missing_type_hint_3(self):
- """Problem here: type hint for i missed"""
- @pedantic
- def calc(n: int, m: int, i) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=38)
-
- def test_missing_type_hint_3_corrected(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- calc(n=42, m=40, i=38)
-
- def test_all_ok_2(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> str:
- return str(n + m + i)
-
- calc(n=42, m=40, i=38)
-
- def test_all_ok_3(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> None:
- str(n + m + i)
-
- calc(n=42, m=40, i=38)
-
- def test_all_ok_4(self):
- @pedantic
- def calc(n: int) -> List[List[int]]:
- return [[n]]
-
- calc(n=42)
-
- def test_all_ok_5(self):
- @pedantic
- def calc(n: int) -> List[Tuple[float, str]]:
- return [(float(n), str(n))]
-
- calc(n=42)
-
- def test_all_ok_6(self):
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[float, str]:
- return n * float(x), str(y)
- return f
-
- calc(n=42)(x=72, y=3.14)
-
- def test_all_ok_7(self):
- @pedantic
- def calc(n: List[List[float]]) -> Any:
- return n[0][0]
-
- calc(n=[[42.0]])
-
- def test_all_ok_8(self):
- @pedantic
- def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
- @pedantic
- def f(x: int, y: float) -> Tuple[float, str]:
- return n * float(x), str(y)
-
- return f
-
- calc(n=42)(x=3, y=3.14)
-
- def test_wrong_type_hint_1(self):
- """Problem here: str != int"""
- @pedantic
- def calc(n: int, m: int, i: int) -> str:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_1_corrected(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> str:
- return str(n + m + i)
-
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_2(self):
- """Problem here: str != int"""
- @pedantic
- def calc(n: int, m: int, i: str) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_2_corrected(self):
- @pedantic
- def calc(n: int, m: int, i: str) -> int:
- return n + m + int(i)
-
- calc(n=42, m=40, i='38')
-
- def test_wrong_type_hint_3(self):
- """Problem here: None != int"""
- @pedantic
- def calc(n: int, m: int, i: int) -> None:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_corrected(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> None:
- print(n + m + i)
-
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_4(self):
- """Problem here: None != int"""
- @pedantic
- def calc(n: int, m: int, i: int) -> int:
- print(n + m + i)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=38)
-
- def test_wrong_type_hint_4_corrected(self):
- @pedantic
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- calc(n=42, m=40, i=38)
-
- def test_none_1(self):
- """Problem here: None is not accepted"""
- @pedantic
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=None)
-
- def test_none_2(self):
- @pedantic
- def calc(n: int, m: int, i: Optional[int]) -> int:
- return n + m + i if i is not None else n + m
-
- calc(n=42, m=40, i=None)
-
- def test_none_3(self):
- @pedantic
- def calc(n: int, m: int, i: Union[int, None]) -> int:
- return n + m + i if i is not None else n + m
-
- calc(n=42, m=40, i=None)
-
- def test_none_4(self):
- """Problem here: function may return None"""
- @pedantic
- def calc(n: int, m: int, i: Union[int, None]) -> int:
- return n + m + i if i is not None else None
-
- calc(n=42, m=40, i=42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(n=42, m=40, i=None)
-
- def test_none_5(self):
- @pedantic
- def calc(n: int, m: int, i: Union[int, None]) -> Optional[int]:
- return n + m + i if i is not None else None
-
- calc(n=42, m=40, i=None)
-
- def test_inheritance_1(self):
- class MyClassA:
- pass
-
- class MyClassB(MyClassA):
- pass
-
- @pedantic
- def calc(a: MyClassA) -> str:
- return str(a)
-
- calc(a=MyClassA())
- calc(a=MyClassB())
-
- def test_inheritance_2(self):
- """Problem here: A is not a subtype of B"""
- class MyClassA:
- pass
-
- class MyClassB(MyClassA):
- pass
-
- @pedantic
- def calc(a: MyClassB) -> str:
- return str(a)
-
- calc(a=MyClassB())
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(a=MyClassA())
-
- def test_instance_method_1(self):
- class MyClassA:
- @pedantic
- def calc(self, i: int) -> str:
- return str(i)
-
- a = MyClassA()
- a.calc(i=42)
-
- def test_instance_method_2(self):
- """Problem here: 'i' has no type annotation"""
- class MyClassA:
- @pedantic
- def calc(self, i) -> str:
- return str(i)
-
- a = MyClassA()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- a.calc(i=42)
-
- def test_instance_method_2_corrected(self):
- class MyClassA:
- @pedantic
- def calc(self, i: int) -> str:
- return str(i)
-
- a = MyClassA()
- a.calc(i=42)
-
- def test_instance_method_int_is_not_float(self):
- class MyClassA:
- @pedantic
- def calc(self, i: float) -> str:
- return str(i)
-
- a = MyClassA()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- a.calc(i=42)
-
- def test_instance_method_3_corrected(self):
- class MyClassA:
- @pedantic
- def calc(self, i: float) -> str:
- return str(i)
-
- a = MyClassA()
- a.calc(i=42.0)
-
- def test_instance_method_no_kwargs(self):
- class MyClassA:
- @pedantic
- def calc(self, i: int) -> str:
- return str(i)
-
- a = MyClassA()
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- a.calc(42)
-
- def test_instance_method_5(self):
- """Problem here: instance methods is not called with kwargs"""
- class MyClassA:
- @pedantic
- def calc(self, i: int) -> str:
- return str(i)
-
- a = MyClassA()
- a.calc(i=42)
-
- def test_lambda_1(self):
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- return lambda x: str(x * i)
-
- calc(i=42.0)(10.0)
-
- def test_lambda_3(self):
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- def res(x: float) -> str:
- return str(x * i)
- return res
-
- calc(i=42.0)(10.0)
-
- def test_lambda_int_is_not_float(self):
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- def res(x: int) -> str:
- return str(x * i)
- return res
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=42.0)(x=10)
-
- def test_lambda_4_almost_corrected(self):
- """Problem here: float != str"""
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- @pedantic
- def res(x: int) -> str:
- return str(x * i)
- return res
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=42.0)(x=10)
-
- def test_lambda_4_almost_corrected_2(self):
- @pedantic
- def calc(i: float) -> Callable[[int], str]:
- @pedantic
- def res(x: int) -> str:
- return str(x * i)
- return res
-
- calc(i=42.0)(x=10)
-
- def test_lambda_5(self):
- """Problem here: float != int"""
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- @pedantic
- def res(x: float) -> str:
- return str(x * i)
- return res
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=42.0)(x=10)
-
- def test_lambda_corrected(self):
- @pedantic
- def calc(i: float) -> Callable[[float], str]:
- @pedantic
- def res(x: float) -> str:
- return str(x * i)
-
- return res
-
- calc(i=42.0)(x=10.0)
-
- def test_tuple_without_type_args(self):
- @pedantic
- def calc(i: Tuple) -> str:
- return str(i)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=(42.0, 43, 'hi'))
-
- def test_tuple_without_args_corrected(self):
- @pedantic
- def calc(i: Tuple[Any, ...]) -> str:
- return str(i)
-
- calc(i=(42.0, 43, 'hi'))
-
- def test_callable_without_type_args(self):
- @pedantic
- def calc(i: Callable) -> str:
- return str(i(' you'))
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=lambda x: (42.0, 43, 'hi', x))
-
- def test_callable_without_args_correct_with_lambdas(self):
- @pedantic
- def calc(i: Callable[[Any], Tuple[Any, ...]]) -> str:
- return str(i(x=' you'))
-
- calc(i=lambda x: (42.0, 43, 'hi', x))
-
- def test_callable_without_args_corrected(self):
- @pedantic
- def calc(i: Callable[[Any], Tuple[Any, ...]]) -> str:
- return str(i(x=' you'))
-
- @pedantic
- def arg(x: Any) -> Tuple[Any, ...]:
- return 42.0, 43, 'hi', x
- calc(i=arg)
-
- def test_list_without_args(self):
- @pedantic
- def calc(i: List) -> Any:
- return [i]
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(i=[42.0, 43, 'hi'])
-
- def test_list_without_args_corrected(self):
- @pedantic
- def calc(i: List[Any]) -> List[List[Any]]:
- return [i]
-
- calc(i=[42.0, 43, 'hi'])
-
- def test_ellipsis_in_callable_1(self):
- @pedantic
- def calc(i: Callable[..., int]) -> int:
- return i()
-
- @pedantic
- def call() -> int:
- return 42
-
- calc(i=call)
-
- def test_ellipsis_in_callable_2(self):
- @pedantic
- def calc(i: Callable[..., int]) -> int:
- return i(x=3.14, y=5)
-
- @pedantic
- def call(x: float, y: int) -> int:
- return 42
-
- calc(i=call)
-
- def test_ellipsis_in_callable_3(self):
- """Problem here: call to "call" misses one argument"""
- @pedantic
- def calc(i: Callable[..., int]) -> int:
- return i(x=3.14)
-
- @pedantic
- def call(x: float, y: int) -> int:
- return 42
-
- with self.assertRaises(expected_exception=PedanticException):
- calc(i=call)
-
- def test_optional_args_1(self):
- @pedantic
- def calc(a: int, b: int = 42) -> int:
- return a + b
-
- calc(a=2)
-
- def test_optional_args_2(self):
- @pedantic
- def calc(a: int = 3, b: int = 42, c: float = 5.0) -> float:
- return a + b + c
-
- calc()
- calc(a=1)
- calc(b=1)
- calc(c=1.0)
- calc(a=1, b=1)
- calc(a=1, c=1.0)
- calc(b=1, c=1.0)
- calc(a=1, b=1, c=1.0)
-
- def test_optional_args_3(self):
- """Problem here: optional argument c: 5 is not a float"""
- @pedantic
- def calc(a: int = 3, b: int = 42, c: float = 5) -> float:
- return a + b + c
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc()
-
- def test_optional_args_3_corrected(self):
- @pedantic
- def calc(a: int = 3, b: int = 42, c: float = 5.0) -> float:
- return a + b + c
-
- calc()
-
- def test_optional_args_4(self):
- class MyClass:
- @pedantic
- def foo(self, a: int, b: Optional[int] = 1) -> int:
- return a + b
-
- my_class = MyClass()
- my_class.foo(a=10)
-
- def test_optional_args_5(self):
- @pedantic
- def calc(d: Optional[Dict[int, int]] = None) -> Optional[int]:
- if d is None:
- return None
- return sum(d.keys())
-
- calc(d=None)
- calc()
- calc(d={42: 3})
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(d={42: 3.14})
-
- def test_optional_args_6(self):
- """"Problem here: str != int"""
- @pedantic
- def calc(d: int = 42) -> int:
- return int(d)
-
- calc(d=99999)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(d='999999')
-
- def test_enum_1(self):
- """Problem here: Type hint for 'a' should be MyEnum instead of MyEnum.GAMMA"""
- class MyEnum(Enum):
- ALPHA = 'startEvent'
- BETA = 'task'
- GAMMA = 'sequenceFlow'
-
- class MyClass:
- @pedantic
- def operation(self, a: MyEnum.GAMMA) -> None:
- print(a)
-
- m = MyClass()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.operation(a=MyEnum.GAMMA)
-
- def test_enum_1_corrected(self):
- class MyEnum(Enum):
- ALPHA = 'startEvent'
- BETA = 'task'
- GAMMA = 'sequenceFlow'
-
- @pedantic
- def operation(a: MyEnum) -> None:
- print(a)
-
- operation(a=MyEnum.GAMMA)
-
- def test_sloppy_types_dict(self):
- @pedantic
- def operation(d: dict) -> int:
- return len(d.keys())
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d={1: 1, 2: 2})
-
- def test_sloppy_types_dict_almost_corrected_no_type_args(self):
- @pedantic
- def operation(d: Dict) -> int:
- return len(d.keys())
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d={1: 1, 2: 2})
-
- def test_sloppy_types_dict_corrected(self):
- @pedantic
- def operation(d: Dict[int, int]) -> int:
- return len(d.keys())
-
- operation(d={1: 1, 2: 2})
-
- def test_sloppy_types_list(self):
- @pedantic
- def operation(d: list) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=[1, 2, 3, 4])
-
- def test_sloppy_types_list_almost_corrected_no_type_args(self):
- @pedantic
- def operation(d: List) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=[1, 2, 3, 4])
-
- def test_sloppy_types_list_corrected(self):
- @pedantic
- def operation(d: List[int]) -> int:
- return len(d)
-
- operation(d=[1, 2, 3, 4])
-
- def test_sloppy_types_tuple(self):
- @pedantic
- def operation(d: tuple) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=(1, 2, 3))
-
- def test_sloppy_types_tuple_almost_corrected_no_type_args(self):
- @pedantic
- def operation(d: Tuple) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=(1, 2, 3))
-
- def test_sloppy_types_tuple_corrected(self):
- @pedantic
- def operation(d: Tuple[int, int, int]) -> int:
- return len(d)
-
- operation(d=(1, 2, 3))
-
- def test_sloppy_types_set(self):
- @pedantic
- def operation(d: set) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d={1, 2, 3})
-
- def test_sloppy_types_set_almost_corrected_to_type_args(self):
- @pedantic
- def operation(d: Set) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d={1, 2, 3})
-
- def test_sloppy_types_set_corrected(self):
- @pedantic
- def operation(d: Set[int]) -> int:
- return len(d)
-
- operation(d={1, 2, 3})
-
- def test_sloppy_types_frozenset(self):
- @pedantic
- def operation(d: frozenset) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=frozenset({1, 2, 3}))
-
- def test_sloppy_types_frozenset_almost_corrected_no_type_args(self):
- @pedantic
- def operation(d: FrozenSet) -> int:
- return len(d)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- operation(d=frozenset({1, 2, 3}))
-
- def test_sloppy_types_frozenset_corrected(self):
- @pedantic
- def operation(d: FrozenSet[int]) -> int:
- return len(d)
-
- operation(d=frozenset({1, 2, 3}))
-
- def test_type_list_but_got_tuple(self):
- @pedantic
- def calc(ls: List[Any]) -> int:
- return len(ls)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- calc(ls=(1, 2, 3))
-
- def test_type_list_corrected(self):
- @pedantic
- def calc(ls: Tuple[Any, ...]) -> int:
- return len(ls)
-
- calc(ls=(1, 2, 3))
-
- def test_any(self):
- @pedantic
- def calc(ls: List[Any]) -> Dict[int, Any]:
- return {i: ls[i] for i in range(0, len(ls))}
-
- calc(ls=[1, 2, 3])
- calc(ls=[1.11, 2.0, 3.0])
- calc(ls=['1', '2', '3'])
- calc(ls=[10.5, '2', (3, 4, 5)])
-
- def test_aliases(self):
- Vector = List[float]
-
- @pedantic
- def scale(scalar: float, vector: Vector) -> Vector:
- return [scalar * num for num in vector]
-
- scale(scalar=2.0, vector=[1.0, -4.2, 5.4])
-
- def test_new_type(self):
- UserId = NewType('UserId', int)
-
- @pedantic
- def get_user_name(user_id: UserId) -> str:
- return str(user_id)
-
- some_id = UserId(524313)
- get_user_name(user_id=some_id)
-
- # the following would be desirable but impossible to check at runtime:
- # with self.assertRaises(expected_exception=AssertionError):
- # get_user_name(user_id=-1)
-
- def test_list_of_new_type(self):
- UserId = NewType('UserId', int)
-
- @pedantic
- def get_user_name(user_ids: List[UserId]) -> str:
- return str(user_ids)
-
- get_user_name(user_ids=[UserId(524313), UserId(42)])
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- get_user_name(user_ids=[UserId(524313), UserId(42), 430.0])
-
- def test_callable_no_args(self):
- @pedantic
- def f(g: Callable[[], str]) -> str:
- return g()
-
- @pedantic
- def greetings() -> str:
- return 'hello world'
-
- f(g=greetings)
-
- def test_type_var(self):
- T = TypeVar('T')
-
- @pedantic
- def first(ls: List[T]) -> T:
- return ls[0]
-
- first(ls=[1, 2, 3])
-
- def test_type_var_wrong(self):
- T = TypeVar('T')
-
- @pedantic
- def first(ls: List[T]) -> T:
- return str(ls[0])
-
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- first(ls=[1, 2, 3])
-
- def test_type_var_wrong_sequence(self):
- T = TypeVar('T')
-
- @pedantic
- def first(ls: Sequence[T]) -> T:
- return str(ls[0])
-
- with self.assertRaises(expected_exception=PedanticTypeVarMismatchException):
- first(ls=[1, 2, 3])
-
- def test_double_pedantic(self):
- @pedantic
- @pedantic
- def f(x: int, y: float) -> Tuple[float, str]:
- return float(x), str(y)
-
- f(x=5, y=3.14)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f(x=5.0, y=3.14)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- f(5, 3.14)
-
- def test_args_kwargs(self):
- @pedantic
- def some_method(a: int = 0, b: float = 0.0) -> float:
- return a * b
-
- @pedantic
- def wrapper_method(*args: Union[int, float], **kwargs: Union[int, float]) -> float:
- return some_method(*args, **kwargs)
-
- some_method()
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- some_method(3, 3.0)
- some_method(a=3, b=3.0)
- wrapper_method()
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- wrapper_method(3, 3.0)
- wrapper_method(a=3, b=3.0)
-
- def test_args_kwargs_no_type_hint(self):
- @pedantic
- def method_no_type_hint(*args, **kwargs) -> None:
- print(args)
- print(kwargs)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- method_no_type_hint(a=3, b=3.0)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- method_no_type_hint()
-
- def test_args_kwargs_wrong_type_hint(self):
- """See: https://www.python.org/dev/peps/pep-0484/#arbitrary-argument-lists-and-default-argument-values"""
- @pedantic
- def wrapper_method(*args: str, **kwargs: str) -> None:
- print(args)
- print(kwargs)
-
- wrapper_method()
- wrapper_method('hi', 'you', ':)')
- wrapper_method(a='hi', b='you', c=':)')
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- wrapper_method('hi', 'you', ':)', 7)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- wrapper_method(3, 3.0)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- wrapper_method(a=3, b=3.0)
-
- def test_additional_kwargs(self):
- @pedantic
- def some_method(a: int, b: float = 0.0, **kwargs: int) -> float:
- return sum([a, b])
-
- some_method(a=5)
- some_method(a=5, b=0.1)
- some_method(a=5, b=0.1, c=4)
- some_method(a=5, b=0.1, c=4, d=5, e=6)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_method(a=5, b=0.1, c=4, d=5.0, e=6)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_method(a=5.0, b=0.1, c=4, d=5, e=6)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- some_method(a=5, b=0, c=4, d=5, e=6)
-
- def test_args_kwargs_different_types(self):
- @pedantic
- def foo(*args: str, **kwds: int) -> None:
- print(args)
- print(kwds)
-
- foo('a', 'b', 'c')
- foo(x=1, y=2)
- foo('', z=0)
-
- def test_pedantic_on_class(self):
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- @pedantic
- class MyClass:
- pass
- MyClass()
-
- def test_is_subtype_tuple(self):
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- @pedantic
- def foo() -> Callable[[Tuple[float, str]], Tuple[int]]:
- def bar(a: Tuple[float]) -> Tuple[int]:
- return len(a[1]) + int(a[0]),
- return bar
- foo()
-
- def test_is_subtype_tuple_corrected(self):
- @pedantic
- def foo() -> Callable[[Tuple[float, str]], Tuple[int]]:
- def bar(a: Tuple[float, str]) -> Tuple[int]:
- return len(a[1]) + int(a[0]),
- return bar
- foo()
-
- def test_forward_ref(self):
- class Conversation:
- pass
-
- @pedantic
- def get_conversations() -> List['Conversation']:
- return [Conversation(), Conversation()]
-
- get_conversations()
-
- def test_alternative_list_type_hint(self):
- @pedantic
- def _is_digit_in_int(digit: [int], num: int) -> bool:
- num_str = str(num)
- for i in num_str:
- if int(i) == digit:
- return True
- return False
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- _is_digit_in_int(digit=4, num=42)
-
- def test_callable_with_union_return(self):
- class MyClass:
- pass
-
- @pedantic
- def admin_required(func: Callable[..., Union[str, MyClass]]) -> Callable[..., Union[str, MyClass]]:
- @wraps(func)
- def decorated_function(*args, **kwargs):
- return func(*args, **kwargs)
- return decorated_function
-
- @admin_required
- @pedantic
- def get_server_info() -> str:
- return 'info'
-
- get_server_info()
-
- def test_pedantic(self):
- @pedantic
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- self.assertEqual('abc', foo(a=4, b='abc'))
-
- def test_pedantic_always(self):
- @pedantic
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- self.assertEqual('abc', foo(a=4, b='abc'))
-
- def test_pedantic_arguments_fail(self):
- @pedantic
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- foo(a=4, b=5)
-
- def test_pedantic_return_type_fail(self):
- @pedantic
- def foo(a: int, b: str) -> str:
- return 6
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- foo(a=4, b='abc')
-
- def test_return_type_none(self):
- @pedantic
- def foo() -> None:
- return 'a'
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- foo()
-
- def test_marco(self):
- @pedantic_class
- class A:
- def __init__(self, val: int) -> None:
- self.val = val
-
- def __eq__(self, other: 'A') -> bool: # other: A and all subclasses
- return self.val == other.val
-
- @pedantic_class
- class B(A):
- def __init__(self, val: int) -> None:
- super().__init__(val=val)
-
- @pedantic_class
- class C(A):
- def __init__(self, val: int) -> None:
- super().__init__(val=val)
-
- a = A(val=42)
- b = B(val=42)
- c = C(val=42)
-
- assert a == b # works
- assert a == c # works
- assert b == c # error
-
- def test_date_datetime(self):
- @pedantic
- def foo(a: datetime, b: date) -> None:
- pass
-
- foo(a=datetime(1995, 2, 5), b=date(1987, 8, 7))
- foo(a=datetime(1995, 2, 5), b=datetime(1987, 8, 7))
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- foo(a=date(1995, 2, 5), b=date(1987, 8, 7))
-
- def test_any_type(self):
- @pedantic
- def foo(a: Any) -> None:
- pass
-
- foo(a='aa')
-
- def test_callable_exact_arg_count(self):
- @pedantic
- def foo(a: Callable[[int, str], int]) -> None:
- pass
-
- def some_callable(x: int, y: str) -> int:
- pass
-
- foo(a=some_callable)
-
- def test_callable_bad_type(self):
- @pedantic
- def foo(a: Callable[..., int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_callable_too_few_arguments(self):
- @pedantic
- def foo(a: Callable[[int, str], int]) -> None:
- pass
-
- def some_callable(x: int) -> int:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=some_callable)
-
- def test_callable_mandatory_kwonlyargs(self):
- @pedantic
- def foo(a: Callable[[int, str], int]) -> None:
- pass
-
- def some_callable(x: int, y: str, *, z: float, bar: str) -> int:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=some_callable)
-
- def test_callable_class(self):
- """
- Test that passing a class as a callable does not count the "self" argument "a"gainst the
- ones declared in the Callable specification.
-
- """
- @pedantic
- def foo(a: Callable[[int, str], Any]) -> None:
- pass
-
- class SomeClass:
- def __init__(self, x: int, y: str):
- pass
-
- foo(a=SomeClass)
-
- def test_callable_plain(self):
- @pedantic
- def foo(a: Callable[..., Any]) -> None:
- pass
-
- def callback(a):
- pass
-
- foo(a=callback)
-
- def test_callable_bound_method(self):
- @pedantic
- def foo(callback: Callable[[int], Any]) -> None:
- pass
-
- foo(callback=Child().method)
-
- def test_callable_defaults(self):
- """
- Test that a callable having "too many" arguments don't raise an error if the extra
- arguments have default values.
-
- """
- @pedantic
- def foo(callback: Callable[[int, str], Any]) -> None:
- pass
-
- def some_callable(x: int, y: str, z: float = 1.2) -> int:
- pass
-
- foo(callback=some_callable)
-
- def test_callable_builtin(self):
- @pedantic
- def foo(callback: types.BuiltinFunctionType) -> None:
- pass
-
- foo(callback=[].append)
-
- def test_dict_bad_type(self):
- @pedantic
- def foo(a: Dict[str, int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_dict_bad_key_type(self):
- @pedantic
- def foo(a: Dict[str, int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a={1: 2})
-
- def test_dict_bad_value_type(self):
- @pedantic
- def foo(a: Dict[str, int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a={'x': 'a'})
-
- def test_list_bad_type(self):
- @pedantic
- def foo(a: List[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_list_bad_element(self):
- @pedantic
- def foo(a: List[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=[1, 2, 'bb'])
-
- def test_sequence_bad_type(self):
- @pedantic
- def foo(a: Sequence[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_sequence_bad_element(self):
- @pedantic
- def foo(a: Sequence[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=[1, 2, 'bb'])
-
- def test_abstractset_custom_type(self):
- T = TypeVar('T')
-
- @pedantic_class
- class DummySet(AbstractSet[T]):
- def __contains__(self, x: object) -> bool:
- return x == 1
-
- def __len__(self) -> T:
- return 1
-
- def __iter__(self) -> Iterator[T]:
- yield 1
-
- @pedantic
- def foo(a: AbstractSet[int]) -> None:
- pass
-
- foo(a=DummySet[int]())
-
- def test_abstractset_bad_type(self):
- @pedantic
- def foo(a: AbstractSet[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_set_bad_type(self):
- @pedantic
- def foo(a: Set[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_abstractset_bad_element(self):
- @pedantic
- def foo(a: AbstractSet[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a={1, 2, 'bb'})
-
- def test_set_bad_element(self):
- @pedantic
- def foo(a: Set[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a={1, 2, 'bb'})
-
- def test_tuple_bad_type(self):
- @pedantic
- def foo(a: Tuple[int]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=5)
-
- def test_tuple_too_many_elements(self):
- @pedantic
- def foo(a: Tuple[int, str]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=(1, 'aa', 2))
-
- def test_tuple_too_few_elements(self):
- @pedantic
- def foo(a: Tuple[int, str]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=(1,))
-
- def test_tuple_bad_element(self):
- @pedantic
- def foo(a: Tuple[int, str]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=(1, 2))
-
- def test_tuple_ellipsis_bad_element(self):
- @pedantic
- def foo(a: Tuple[int, ...]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=(1, 2, 'blah'))
-
- def test_namedtuple(self):
- Employee = NamedTuple('Employee', [('name', str), ('id', int)])
-
- @pedantic
- def foo(bar: Employee) -> None:
- print(bar)
-
- foo(bar=Employee('bob', 1))
-
- def test_namedtuple_key_mismatch(self):
- Employee1 = NamedTuple('Employee', [('name', str), ('id', int)])
- Employee2 = NamedTuple('Employee', [('firstname', str), ('id', int)])
-
- @pedantic
- def foo(bar: Employee1) -> None:
- print(bar)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(bar=Employee2('bob', 1))
-
- def test_namedtuple_type_mismatch(self):
- Employee = NamedTuple('Employee', [('name', str), ('id', int)])
-
- @pedantic
- def foo(bar: Employee) -> None:
- print(bar)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(bar=('bob', 1))
-
- def test_namedtuple_huge_type_mismatch(self):
- Employee = NamedTuple('Employee', [('name', str), ('id', int)])
-
- @pedantic
- def foo(bar: int) -> None:
- print(bar)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(bar=foo(bar=Employee('bob', 1)))
-
- def test_namedtuple_wrong_field_type(self):
- Employee = NamedTuple('Employee', [('name', str), ('id', int)])
-
- @pedantic
- def foo(bar: Employee) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(bar=Employee(2, 1))
-
- def test_union(self):
- @pedantic
- def foo(a: Union[str, int]) -> None:
- pass
-
- for value in [6, 'xa']:
- foo(a=value)
-
- def test_union_new_syntax(self):
- @pedantic
- def foo(a: str | int) -> None:
- pass
-
- for value in [6, 'xa']:
- foo(a=value)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1.7)
-
- def test_union_typing_type(self):
- @pedantic
- def foo(a: Union[str, Collection]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1)
-
- def test_union_fail(self):
- @pedantic
- def foo(a: Union[str, int]) -> None:
- pass
-
- for value in [5.6, b'xa']:
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=value)
-
- def test_type_var_constraints(self):
- T = TypeVar('T', int, str)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- for values in [
- {'a': 6, 'b': 7},
- {'a': 'aa', 'b': "bb"},
- ]:
- foo(**values)
-
- def test_type_var_constraints_fail_typing_type(self):
- T = TypeVar('T', int, Collection)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a='aa', b='bb')
-
- def test_typevar_constraints_fail(self):
- T = TypeVar('T', int, str)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=2.5, b='aa')
-
- def test_typevar_bound(self):
- T = TypeVar('T', bound=Parent)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- foo(a=Child(), b=Child())
-
- def test_type_var_bound_fail(self):
- T = TypeVar('T', bound=Child)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=Parent(), b=Parent())
-
- def test_type_var_invariant_fail(self):
- T = TypeVar('T', int, str)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=2, b=3.6)
-
- def test_type_var_covariant(self):
- T = TypeVar('T', covariant=True)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- foo(a=Parent(), b=Child())
-
- def test_type_var_covariant_fail(self):
- T = TypeVar('T', covariant=True)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeVarMismatchException):
- foo(a=Child(), b=Parent())
-
- def test_type_var_contravariant(self):
- T = TypeVar('T', contravariant=True)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- foo(a=Child(), b=Parent())
-
- def test_type_var_contravariant_fail(self):
- T = TypeVar('T', contravariant=True)
-
- @pedantic
- def foo(a: T, b: T) -> None:
- pass
-
- with self.assertRaises(PedanticTypeVarMismatchException):
- foo(a=Parent(), b=Child())
-
- def test_class_bad_subclass(self):
- @pedantic
- def foo(a: Type[Child]) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=Parent)
-
- def test_class_any(self):
- @pedantic
- def foo(a: Type[Any]) -> None:
- pass
-
- foo(a=str)
-
- def test_wrapped_function(self):
- def decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(*args, **kwargs)
- return wrapper
-
- @pedantic
- @decorator
- def foo(a: 'Child') -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=Parent())
-
- def test_mismatching_default_type(self):
- @pedantic
- def foo(a: str = 1) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo()
-
- def test_implicit_default_none(self):
- """
- Test that if the default value is ``None``, a ``None`` argument can be passed.
-
- """
- @pedantic
- def foo(a: Optional[str] = None) -> None:
- pass
-
- foo()
-
- def test_generator_simple(self):
- """Test that argument type checking works in a generator function too."""
- @pedantic
- def generate(a: int) -> Generator[int, int, None]:
- yield a
- yield a + 1
-
- gen = generate(a=1)
- next(gen)
-
- def test_wrapped_generator_no_return_type_annotation(self):
- """Test that return type checking works in a generator function too."""
- @pedantic
- def generate(a: int) -> Generator[int, int, None]:
- yield a
- yield a + 1
-
- gen = generate(a=1)
- next(gen)
-
- def test_varargs(self):
- @pedantic
- def foo(*args: int) -> None:
- pass
-
- foo(1, 2)
-
- def test_varargs_fail(self):
- @pedantic
- def foo(*args: int) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(1, 'a')
-
- def test_kwargs(self):
- @pedantic
- def foo(**kwargs: int) -> None:
- pass
-
- foo(a=1, b=2)
-
- def test_kwargs_fail(self):
- @pedantic
- def foo(**kwargs: int) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1, b='a')
-
- def test_generic(self):
- T_Foo = TypeVar('T_Foo')
-
- class FooGeneric(Generic[T_Foo]):
- pass
-
- @pedantic
- def foo(a: FooGeneric[str]) -> None:
- print(a)
-
- foo(a=FooGeneric[str]())
-
- def test_newtype(self):
- myint = NewType("myint", int)
-
- @pedantic
- def foo(a: myint) -> int:
- return 42
-
- assert foo(a=1) == 42
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a="a")
-
- def test_collection(self):
- @pedantic
- def foo(a: Collection) -> None:
- pass
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=True)
-
- def test_binary_io(self):
- @pedantic
- def foo(a: BinaryIO) -> None:
- print(a)
-
- foo(a=BytesIO())
-
- def test_text_io(self):
- @pedantic
- def foo(a: TextIO) -> None:
- print(a)
-
- foo(a=StringIO())
-
- def test_binary_io_fail(self):
- @pedantic
- def foo(a: TextIO) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=BytesIO())
-
- def test_text_io_fail(self):
- @pedantic
- def foo(a: BinaryIO) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=StringIO())
-
- def test_binary_io_real_file(self):
- @pedantic
- def foo(a: BinaryIO) -> None:
- print(a)
-
- with open(file=TEST_FILE, mode='wb') as f:
- foo(a=f)
-
- def test_text_io_real_file(self):
- @pedantic
- def foo(a: TextIO) -> None:
- print(a)
-
- with open(file=TEST_FILE, mode='w') as f:
- foo(a=f)
-
- def test_pedantic_return_type_var_fail(self):
- T = TypeVar('T', int, float)
-
- @pedantic
- def foo(a: T, b: T) -> T:
- return 'a'
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=4, b=2)
-
- def test_callable(self):
- @pedantic
- def foo_1(a: Callable[..., int]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Callable) -> None:
- print(a)
-
- def some_callable() -> int:
- return 4
-
- foo_1(a=some_callable)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a=some_callable)
-
- def test_list(self):
- @pedantic
- def foo_1(a: List[int]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: List) -> None:
- print(a)
-
- @pedantic
- def foo_3(a: list) -> None:
- print(a)
-
- @pedantic
- def foo_4(a: list[int]) -> None:
- print(a)
-
- foo_1(a=[1, 2])
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a=[1, 2])
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_3(a=[1, 2])
-
-
- foo_4(a=[1, 2])
-
- def test_dict(self):
- @pedantic
- def foo_1(a: Dict[str, int]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Dict) -> None:
- print(a)
-
- @pedantic
- def foo_3(a: dict) -> None:
- print(a)
-
- @pedantic
- def foo_4(a: dict[str, int]) -> None:
- print(a)
-
- foo_1(a={'x': 2})
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a={'x': 2})
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_3(a={'x': 2})
-
- foo_4(a={'x': 2})
-
- def test_sequence(self):
- @pedantic
- def foo(a: Sequence[str]) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- foo(a=value)
-
- def test_sequence_no_type_args(self):
- @pedantic
- def foo(a: Sequence) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=value)
-
- def test_iterable(self):
- @pedantic
- def foo(a: Iterable[str]) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- foo(a=value)
-
- def test_iterable_no_type_args(self):
- @pedantic
- def foo(a: Iterable) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=value)
-
- def test_container(self):
- @pedantic
- def foo(a: Container[str]) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- foo(a=value)
-
- def test_container_no_type_args(self):
- @pedantic
- def foo(a: Container) -> None:
- print(a)
-
- for value in [('a', 'b'), ['a', 'b'], 'abc']:
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=value)
-
- def test_set(self):
- @pedantic
- def foo_1(a: AbstractSet[int]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Set[int]) -> None:
- print(a)
-
- for value in [set(), {6}]:
- foo_1(a=value)
- foo_2(a=value)
-
- def test_set_no_type_args(self):
- @pedantic
- def foo_1(a: AbstractSet) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Set) -> None:
- print(a)
-
- @pedantic
- def foo_3(a: set) -> None:
- print(a)
-
- for value in [set(), {6}]:
- with self.assertRaises(PedanticTypeCheckException):
- foo_1(a=value)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a=value)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_3(a=value)
-
- def test_tuple(self):
- @pedantic
- def foo_1(a: Tuple[int, int]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Tuple[int, ...]) -> None:
- print(a)
-
- foo_1(a=(1, 2))
- foo_2(a=(1, 2))
-
- def test_tuple_no_type_args(self):
- @pedantic
- def foo_1(a: Tuple) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: tuple) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_1(a=(1, 2))
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a=(1, 2))
-
- def test_empty_tuple(self):
- @pedantic
- def foo(a: Tuple[()]) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=())
-
- def test_class(self):
- @pedantic
- def foo_1(a: Type[Parent]) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: Type[TypeVar('UnboundType')]) -> None:
- print(a)
-
- @pedantic
- def foo_3(a: Type[TypeVar('BoundType', bound=Parent)]) -> None:
- print(a)
-
- foo_1(a=Child)
- foo_2(a=Child)
- foo_3(a=Child)
-
- def test_class_no_type_vars(self):
- @pedantic
- def foo_1(a: Type) -> None:
- print(a)
-
- @pedantic
- def foo_2(a: type) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_1(a=Child)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo_2(a=Child)
-
- def test_class_not_a_class(self):
- @pedantic
- def foo(a: Type[Parent]) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1)
-
- def test_complex(self):
- @pedantic
- def foo(a: complex) -> None:
- print(a)
-
- foo(a=complex(1, 5))
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1.0)
-
- def test_float(self):
- @pedantic
- def foo(a: float) -> None:
- print(a)
-
- foo(a=1.5)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=1)
-
- def test_coroutine_correct_return_type(self):
- @pedantic
- async def foo() -> str:
- return 'foo'
-
- coro = foo()
-
- with self.assertRaises(StopIteration):
- coro.send(None)
-
- def test_coroutine_wrong_return_type(self):
- @pedantic
- async def foo() -> str:
- return 1
-
- coro = foo()
-
- with self.assertRaises(PedanticTypeCheckException):
- coro.send(None)
-
- def test_bytearray_bytes(self):
- @pedantic
- def foo(x: bytearray) -> None:
- pass
-
- foo(x=bytearray([1]))
-
- def test_class_decorator(self):
- @pedantic_class
- class Foo:
- @staticmethod
- def staticmethod() -> int:
- return 'foo'
-
- @classmethod
- def classmethod(cls) -> int:
- return 'foo'
-
- def method(self) -> int:
- return 'foo'
-
- with self.assertRaises(PedanticTypeCheckException):
- Foo.staticmethod()
-
- with self.assertRaises(PedanticTypeCheckException):
- Foo.classmethod()
-
- with self.assertRaises(PedanticTypeCheckException):
- Foo().method()
-
- def test_generator(self):
- @pedantic
- def genfunc() -> Generator[int, str, List[str]]:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- gen = genfunc()
-
- with self.assertRaises(StopIteration):
- value = next(gen)
- while True:
- value = gen.send(str(value))
- assert isinstance(value, int)
-
- def test_generator_no_type_args(self):
- @pedantic
- def genfunc() -> Generator:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- with self.assertRaises(PedanticTypeCheckException):
- genfunc()
-
- def test_iterator(self):
- @pedantic
- def genfunc() -> Iterator[int]:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- gen = genfunc()
-
- with self.assertRaises(PedanticTypeCheckException):
- value = next(gen)
- while True:
- value = gen.send(str(value))
- assert isinstance(value, int)
-
- def test_iterator_no_type_args(self):
- @pedantic
- def genfunc() -> Iterator:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- with self.assertRaises(PedanticTypeCheckException):
- genfunc()
-
- def test_iterable_advanced(self):
- @pedantic
- def genfunc() -> Iterable[int]:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- gen = genfunc()
-
- with self.assertRaises(PedanticTypeCheckException):
- value = next(gen)
- while True:
- value = gen.send(str(value))
- assert isinstance(value, int)
-
- def test_iterable_advanced_no_type_args(self):
- @pedantic
- def genfunc() -> Iterable:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- with self.assertRaises(PedanticTypeCheckException):
- genfunc()
-
- def test_generator_bad_yield(self):
- @pedantic
- def genfunc_1() -> Generator[int, str, None]:
- yield 'foo'
-
- @pedantic
- def genfunc_2() -> Iterable[int]:
- yield 'foo'
-
- @pedantic
- def genfunc_3() -> Iterator[int]:
- yield 'foo'
-
- gen = genfunc_1()
-
- with self.assertRaises(PedanticTypeCheckException):
- next(gen)
-
- gen = genfunc_2()
-
- with self.assertRaises(PedanticTypeCheckException):
- next(gen)
-
- gen = genfunc_3()
-
- with self.assertRaises(PedanticTypeCheckException):
- next(gen)
-
- def test_generator_bad_send(self):
- @pedantic
- def genfunc() -> Generator[int, str, None]:
- yield 1
- yield 2
-
- gen = genfunc()
- next(gen)
-
- with self.assertRaises(PedanticTypeCheckException):
- gen.send(2)
-
- def test_generator_bad_return(self):
- @pedantic
- def genfunc() -> Generator[int, str, str]:
- yield 1
- return 6
-
- gen = genfunc()
- next(gen)
-
- with self.assertRaises(PedanticTypeCheckException):
- gen.send('foo')
-
- def test_return_generator(self):
- @pedantic
- def genfunc() -> Generator[int, None, None]:
- yield 1
-
- @pedantic
- def foo() -> Generator[int, None, None]:
- return genfunc()
-
- foo()
-
- def test_local_class(self):
- @pedantic_class
- class LocalClass:
- class Inner:
- pass
-
- def create_inner(self) -> 'Inner':
- return self.Inner()
-
- retval = LocalClass().create_inner()
- assert isinstance(retval, LocalClass.Inner)
-
- def test_local_class_async(self):
- @pedantic_class
- class LocalClass:
- class Inner:
- pass
-
- async def create_inner(self) -> 'Inner':
- return self.Inner()
-
- coro = LocalClass().create_inner()
-
- with self.assertRaises(StopIteration):
- coro.send(None)
-
- def test_callable_nonmember(self):
- class CallableClass:
- def __call__(self):
- pass
-
- @pedantic_class
- class LocalClass:
- some_callable = CallableClass()
-
- def test_inherited_class_method(self):
- @pedantic_class
- class Parent:
- @classmethod
- def foo(cls, x: str) -> str:
- return cls.__name__
-
- @pedantic_class
- class Child(Parent):
- pass
-
- self.assertEqual('Parent', Child.foo(x='bar'))
-
- with self.assertRaises(PedanticTypeCheckException):
- Child.foo(x=1)
-
- def test_type_var_forward_ref_bound(self):
- TBound = TypeVar('TBound', bound='Parent')
-
- @pedantic
- def func(x: TBound) -> None:
- pass
-
- func(x=Parent())
-
- with self.assertRaises(PedanticTypeCheckException):
- func(x='foo')
-
- def test_noreturn(self):
- @pedantic
- def foo() -> NoReturn:
- pass
-
- @pedantic
- def bar() -> NoReturn:
- raise ZeroDivisionError('bar')
-
- with self.assertRaises(expected_exception=ZeroDivisionError):
- bar()
-
- with self.assertRaises(PedanticTypeCheckException):
- foo()
-
- def test_literal(self):
- @pedantic
- def foo(a: Literal[1, True, 'x', b'y', 404]) -> None:
- print(a)
-
- foo(a=404)
- foo(a=True)
- foo(a='x')
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=4)
-
- def test_literal_union(self):
- @pedantic
- def foo(a: Union[str, Literal[1, 6, 8]]) -> None:
- print(a)
-
- foo(a=6)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=4)
-
- def test_literal_illegal_value(self):
- @pedantic
- def foo(a: Literal[1, 1.1]) -> None:
- print(a)
-
- with self.assertRaises(PedanticTypeCheckException):
- foo(a=4)
-
- def test_enum(self):
- with self.assertRaises(PedanticTypeCheckException):
- @pedantic_class
- class MyEnum(Enum):
- A = 'a'
-
- def test_enum_aggregate(self):
- T = TypeVar('T', bound=IntEnum)
-
- @pedantic_class
- class EnumAggregate(Generic[T]):
- enum: ClassVar[Type[T]]
-
- def __init__(self, value: Union[int, str, List[T]]) -> None:
- assert len(self.enum) < 10
-
- if value == '':
- raise ValueError(f'Parameter "value" cannot be empty!')
-
- if isinstance(value, list):
- self._value = ''.join([str(x.value) for x in value])
- else:
- self._value = str(value)
-
- self._value = ''.join(sorted(self._value)) # sort characters in string
- self.to_list() # check if is valid
-
- def __contains__(self, item: T) -> bool:
- return item in self.to_list()
-
- def __eq__(self, other: Union['EnumAggregate', str]) -> bool:
- if isinstance(other, str):
- return self._value == other
-
- return self._value == other._value
-
- def __str__(self) -> str:
- return self._value
-
- def to_list(self) -> List[T]:
- return [self.enum(int(character)) for character in self._value]
-
- @property
- def value(self) -> str:
- return self._value
-
- @classmethod
- def all(cls) -> str:
- return ''.join([str(x.value) for x in cls.enum])
-
- class Gender(IntEnum):
- MALE = 1
- FEMALE = 2
- DIVERS = 3
-
- @pedantic_class
- class Genders(EnumAggregate[Gender]):
- enum = Gender
-
- Genders(value=12)
-
- with self.assertRaises(PedanticTypeCheckException):
- Genders(value=Child())
-
- def test_primitive_list_dict_tuple(self):
- @pedantic
- def f(x: list[dict[int, tuple[float, str]]]) -> list[Any]:
- return x
-
- f(x=[{3: (3.24, 'hi')}])
-
- for value in [
- [{3, (3, 'hi')}],
- [{3: (3, 'hi')}],
- [{3: (3.24, 3)}],
- [{3: (3.24, 'hi')}, {0}],
- ]:
- with self.assertRaises(PedanticTypeCheckException):
- f(x=value)
-
- def test_dataclass_protocol(self):
- class IsDataclass(typing.Protocol):
- __dataclass_fields__: ClassVar[Dict]
-
- @dataclass
- class Foo:
- v: int
-
- @pedantic
- def foo(x: IsDataclass) -> IsDataclass:
- return x
-
- foo(x=Foo(v=42))
-
- def test_dataclass_protocol_in_type(self):
- class IsDataclass(typing.Protocol):
- __dataclass_fields__: ClassVar[Dict]
-
- @dataclass
- class Foo:
- v: int
-
- @pedantic
- def foo(x: type[IsDataclass]) -> IsDataclass:
- return x
-
- assert foo(x=Foo) == Foo
-
- def test_dataclass_protocol_in_type_with_union(self):
- class IsDataclass(typing.Protocol):
- __dataclass_fields__: ClassVar[Dict]
-
- @dataclass
- class Foo:
- v: int
-
- @pedantic
- def foo(x: type[None | bool | IsDataclass]) -> IsDataclass:
- return x
-
- assert foo(x=Foo) == Foo
-
- def test_partial_function(self):
- @pedantic
- def f(a: int, b: int) -> int:
- return a + b
-
- g = pedantic(partial(f, a=1))
-
- assert f(a=2, b=3) == 5
- assert g(b=3) == 4
-
- with self.assertRaises(PedanticTypeCheckException):
- f(a='2', b=3)
-
- with self.assertRaises(PedanticTypeCheckException):
- g(b='2')
diff --git a/pedantic/tests/tests_pedantic_async.py b/pedantic/tests/tests_pedantic_async.py
deleted file mode 100644
index 057ae358..00000000
--- a/pedantic/tests/tests_pedantic_async.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import asyncio
-import unittest
-
-from pedantic.decorators.class_decorators import pedantic_class
-from pedantic.exceptions import PedanticTypeCheckException
-from pedantic.decorators.fn_deco_pedantic import pedantic
-
-
-class TestPedanticAsyncio(unittest.IsolatedAsyncioTestCase):
- async def test_coroutine_correct_return_type(self):
- @pedantic
- async def foo() -> str:
- await asyncio.sleep(0)
- return 'foo'
-
- await foo()
-
- async def test_coroutine_wrong_return_type(self):
- @pedantic
- async def foo() -> str:
- await asyncio.sleep(0)
- return 1
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- await foo()
-
- async def test_coroutine_wrong_argument_type(self):
- @pedantic
- async def foo(x: int) -> int:
- await asyncio.sleep(0)
- return 1 + x
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- await foo(x=4.5)
-
- async def test_static_async(self):
- @pedantic_class
- class Foo:
- @staticmethod
- async def staticmethod() -> int:
- await asyncio.sleep(0)
- return 'foo'
-
- @classmethod
- async def classmethod(cls) -> int:
- await asyncio.sleep(0)
- return 'foo'
-
- async def method(self) -> int:
- await asyncio.sleep(0)
- return 'foo'
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- await Foo.staticmethod()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- await Foo.classmethod()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- await Foo().method()
diff --git a/pedantic/tests/tests_pedantic_class.py b/pedantic/tests/tests_pedantic_class.py
deleted file mode 100644
index bf203605..00000000
--- a/pedantic/tests/tests_pedantic_class.py
+++ /dev/null
@@ -1,537 +0,0 @@
-import sys
-import unittest
-from abc import ABC, abstractmethod
-from dataclasses import dataclass
-from enum import IntEnum
-from typing import Any, Optional, Callable, Union, Dict, List
-
-from pedantic.env_var_logic import disable_pedantic, enable_pedantic
-from pedantic import overrides
-from pedantic.decorators.class_decorators import pedantic_class
-from pedantic.exceptions import PedanticOverrideException, PedanticTypeCheckException, \
- PedanticCallWithArgsException
-
-
-class TestPedanticClass(unittest.TestCase):
- def tearDown(self) -> None:
- enable_pedantic()
-
- def test_constructor(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int) -> None:
- self.a = a
-
- MyClass(a=42)
-
- def test_constructor_with_list(self):
- class Foo(IntEnum):
- A = 1
- B = 2
-
- @pedantic_class
- class MyClass:
- def __init__(self, b: int, a: List[Foo]) -> None:
- self.a = a
- self.b = b
-
- MyClass(b=42, a=[Foo.A, Foo.B])
-
- def test_constructor_param_without_type_hint(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a) -> None:
- self.a = a
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass(a=42)
-
- def test_constructor_without_return_type(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int):
- self.a = a
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass(a=42)
-
- def test_constructor_wrong_return_type(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int) -> int:
- self.a = a
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass(a=42)
-
- def test_constructor_must_be_called_with_kwargs(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int) -> None:
- self.a = a
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- MyClass(42)
-
- def test_multiple_methods(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int) -> None:
- self.a = a
-
- def calc(self, b: int) -> int:
- return self.a - b
-
- def print(self, s: str) -> None:
- print(f'{self.a} and {s}')
-
- m = MyClass(a=5)
- m.calc(b=42)
- m.print(s='Hi')
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.calc(45)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.calc(b=45.0)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.print('Hi')
-
- def test_multiple_methods_with_missing_and_wrong_type_hints(self):
- @pedantic_class
- class MyClass:
- def __init__(self, a: int) -> None:
- self.a = a
-
- def calc(self, b: int) -> float:
- return self.a - b
-
- def dream(self, b) -> int:
- return self.a * b
-
- def print(self, s: str):
- print(f'{self.a} and {s}')
-
- m = MyClass(a=5)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.calc(b=42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.print(s='Hi')
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.dream(b=2)
-
- def test_type_annotation_string(self):
- @pedantic_class
- class MyClass:
- def __init__(self, s: str) -> None:
- self.s = s
-
- @staticmethod
- def generator() -> 'MyClass':
- return MyClass(s='generated')
-
- MyClass.generator()
-
- def test_typo_in_type_annotation_string(self):
- @pedantic_class
- class MyClass:
- def __init__(self, s: str) -> None:
- self.s = s
-
- @staticmethod
- def generator() -> 'MyClas':
- return MyClass(s='generated')
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.generator()
-
- def test_overriding_contains(self):
- @pedantic_class
- class MyClass(list):
- def __contains__(self, item: int) -> bool:
- return True
-
- m = MyClass()
- print(42 in m)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- print('something' in m)
-
- def test_type_annotation_string_typo(self):
- @pedantic_class
- class MyClass:
- def compare(self, other: 'MyClas') -> bool:
- return self == other
-
- def fixed_compare(self, other: 'MyClass') -> bool:
- return self == other
-
- m = MyClass()
- m.fixed_compare(other=m)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.compare(other=m)
-
- def test_docstring_not_required(self):
- @pedantic_class
- class Foo:
- def __init__(self, a: int) -> None:
- self.a = a
-
- def bunk(self) -> int:
- '''
- Function with correct docstring. Yes, single-quoted docstrings are allowed too.
- Returns:
- int: 42
- '''
- return self.a
-
- foo = Foo(a=10)
- foo.bunk()
-
- def test_overrides(self):
- @pedantic_class
- class Abstract:
- def func(self, b: str) -> str:
- pass
-
- def bunk(self) -> int:
- pass
-
- @pedantic_class
- class Foo(Abstract):
- def __init__(self, a: int) -> None:
- self.a = a
-
- @overrides(Abstract)
- def func(self, b: str) -> str:
- return b
-
- @overrides(Abstract)
- def bunk(self) -> int:
- return 42
-
- f = Foo(a=42)
- f.func(b='Hi')
- f.bunk()
-
- def test_overrides_abc(self):
- @pedantic_class
- class Abstract(ABC):
- @abstractmethod
- def func(self, b: str) -> str:
- pass
-
- @abstractmethod
- def bunk(self) -> int:
- pass
-
- @pedantic_class
- class Foo(Abstract):
- def __init__(self, a: int) -> None:
- self.a = a
-
- @overrides(Abstract)
- def func(self, b: str) -> str:
- return b
-
- @overrides(Abstract)
- def bunk(self) -> int:
- return 42
-
- f = Foo(a=42)
- f.func(b='Hi')
- f.bunk()
-
- def test_overrides_with_type_errors_and_call_by_args3(self):
- @pedantic_class
- class Abstract:
- def func(self, b: str) -> str:
- pass
-
- def bunk(self) -> int:
- pass
-
- @pedantic_class
- class Foo(Abstract):
- def __init__(self, a: int) -> None:
- self.a = a
-
- @overrides(Abstract)
- def func(self, b: str) -> str:
- return b
-
- @overrides(Abstract)
- def bunk(self) -> int:
- return self.a
-
- f = Foo(a=42)
- f.func(b='Hi')
- f.bunk()
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- f.func('Hi')
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo(a=3.1415)
- f.a = 3.145
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f.bunk()
-
- def test_overrides_goes_wrong(self):
- @pedantic_class
- class Parent:
- def func(self, b: str) -> str:
- return b + b + b
-
- def bunk(self) -> int:
- return 42
-
- with self.assertRaises(expected_exception=PedanticOverrideException):
- @pedantic_class
- class Foo(Parent):
- def __init__(self, a: int) -> None:
- self.a = a
-
- @overrides(Parent)
- def funcy(self, b: str) -> str:
- return b
-
- @overrides(Parent)
- def bunk(self) -> int:
- return self.a
-
- f = Foo(a=40002)
- f.func(b='Hi')
- f.bunk()
-
- p = Parent()
- p.func(b='Hi')
- p.bunk()
-
- def test_static_method_with_sloppy_type_annotation(self):
- @pedantic_class
- class MyStaticClass:
- @staticmethod
- def double_func(a: int) -> int:
- x, y = MyStaticClass.static_bar()
- return x
-
- @staticmethod
- def static_bar() -> (int, int): # this is wrong. Correct would be Tuple[int, int]
- return 0, 1
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- print(MyStaticClass.double_func(a=0))
-
- def test_property(self):
- @pedantic_class
- class MyClass(object):
- def __init__(self, some_arg: Any) -> None:
- self._some_attribute = some_arg
-
- @property
- def some_attribute(self) -> int:
- return self._some_attribute
-
- @some_attribute.setter
- def some_attribute(self, value: str) -> None:
- self._some_attribute = value
-
- def calc(self, value: float) -> float:
- return 2 * value
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- MyClass(42)
-
- m = MyClass(some_arg=42)
- self.assertEqual(m.some_attribute, 42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_attribute = 100
- self.assertEqual(m.some_attribute, 42)
- m.some_attribute = '100'
- self.assertEqual(m._some_attribute, '100')
- m.calc(value=42.0)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- print(m.some_attribute)
-
- def test_property_getter_and_setter_misses_type_hints(self):
- @pedantic_class
- class MyClass(object):
- def __init__(self, some_arg: int) -> None:
- self._some_attribute = some_arg
-
- @property
- def some_attribute(self):
- return self._some_attribute
-
- @some_attribute.setter
- def some_attribute(self, value: int):
- self._some_attribute = value
-
- def calc(self, value: float) -> float:
- return 2 * value
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- MyClass(42)
-
- m = MyClass(some_arg=42)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.some_attribute = 100
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- print(m.some_attribute)
- m.calc(value=42.0)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.calc(value=42)
-
- def test_default_constructor(self):
- @pedantic_class
- class MyClass:
- def fun(self) -> int:
- return 42
- m = MyClass()
- m.fun()
-
- def test_optional_callable(self):
- @pedantic_class
- class SemanticSimilarity:
- def __init__(self, post_processing: bool = True, val: Optional[Callable[[float], float]] = None) -> None:
- if post_processing is None:
- self.post_processing = val
- else:
- self.post_processing = lambda x: x
-
- SemanticSimilarity()
-
- def test_optional_lambda(self):
- @pedantic_class
- class SemanticSimilarity:
- def __init__(self, val: Callable[[float], float] = lambda x: x) -> None:
- self.post_processing = val
-
- SemanticSimilarity()
-
- def test_class_method_type_annotation_missing(self):
- @pedantic_class
- class MyClass:
- @classmethod
- def do(cls):
- print('i did something')
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.do()
-
- def test_class_method_type_annotation(self):
- @pedantic_class
- class MyClass:
- @classmethod
- def do(cls) -> None:
- print('i did something')
-
- @classmethod
- def calc(cls, x: Union[int, float]) -> int:
- return x * x
-
- MyClass.do()
- MyClass.calc(x=5)
- m = MyClass()
- m.do()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.calc(5)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.calc(x=5.1)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- MyClass.calc('hi')
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.calc(5)
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- m.calc(x=5.1)
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- m.calc('hi')
-
- def test_dataclass_inside(self):
- """Pedantic cannot be used on dataclasses."""
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- @pedantic_class
- @dataclass
- class MyClass:
- name: str
- unit_price: float
- quantity_on_hand: int = 0
-
- def test_dataclass_outside(self):
- """Pedantic cannot check the constructor of dataclasses"""
-
- @dataclass
- @pedantic_class
- class MyClass:
- name: str
- unit_price: float
- quantity_on_hand: int = 0
-
- def total_cost(self) -> int:
- return self.unit_price * self.quantity_on_hand
-
- MyClass(name='name', unit_price=5.1)
- a = MyClass(name='name', unit_price=5.0, quantity_on_hand=42)
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- a.total_cost()
-
- def test_class_decorator_static_class_method(self):
- @pedantic_class
- class Foo:
- @staticmethod
- def staticmethod() -> int:
- return 'foo'
-
- @classmethod
- def classmethod(cls) -> int:
- return 'foo'
-
- def method(self) -> int:
- return 'foo'
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo.staticmethod()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo.classmethod()
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo().method()
-
- def test_pedantic_class_disable_pedantic(self):
- disable_pedantic()
-
- @pedantic_class
- class MyClass:
- def __init__(self, pw, **kwargs):
- self._validate_str_len(new_values=kwargs)
-
- @staticmethod
- def _validate_str_len(new_values: Dict[str, Any]) -> None:
- return 42
-
- def method(pw, **kwargs):
- MyClass._validate_str_len(new_values=kwargs)
-
- MyClass._validate_str_len(None)
- MyClass._validate_str_len(new_values={1: 1, 2: 2})
- MyClass(name='hi', age=12, pw='123')
-
- def test_disable_pedantic_2(self):
- """ https://github.com/LostInDarkMath/pedantic-python-decorators/issues/37 """
-
- disable_pedantic()
-
- @pedantic_class
- class Foo:
- def __init__(self) -> None:
- self._value = 42
-
- def do(self) -> None:
- print(self.bar(value=self._value))
-
- @staticmethod
- def bar(value: int) -> int:
- return value + 75
-
- f = Foo()
- f.do()
diff --git a/pedantic/tests/tests_pedantic_class_docstring.py b/pedantic/tests/tests_pedantic_class_docstring.py
deleted file mode 100644
index 61ceeaab..00000000
--- a/pedantic/tests/tests_pedantic_class_docstring.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import unittest
-
-from pedantic import pedantic_class_require_docstring
-from pedantic.exceptions import PedanticDocstringException
-
-
-class TestPedanticClassDocstring(unittest.TestCase):
- def test_require_docstring(self):
- @pedantic_class_require_docstring
- class MyClass:
- def __init__(self, s: str) -> None:
- """Constructor
-
- Args:
- s (str): name
- """
- self.s = s
-
- def double(self, b: int) -> str:
- """some method
-
- Args:
- b (int): magic number
-
- Returns:
- str: cool stuff
-
- """
- return self.s + str(b)
-
- @staticmethod
- def generator() -> 'MyClass':
- """Static
-
- Returns:
- MyClass: instance
- """
- return MyClass(s='generated')
-
- m = MyClass.generator()
- m.double(b=42)
-
- def test_typo_docstring(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_class_require_docstring
- class MyClass:
- def __init__(self, s: str) -> None:
- """Constructor
-
- Args:
- s (str): name
- """
- self.s = s
-
- @staticmethod
- def generator() -> 'MyClass':
- """Static
-
- Returns:
- MyClas: instance
- """
- return MyClass(s='generated')
-
- def test_wrong_docstring(self):
- with self.assertRaises(expected_exception=PedanticDocstringException):
- @pedantic_class_require_docstring
- class MyClass:
- def __init__(self, s: str) -> None:
- """Constructor
-
- Args:
- s (str): name
- """
- self.s = s
-
- def double(self, b: int) -> str:
- """some method
-
- Args:
- b (float): magic number
-
- Returns:
- str: cool stuff
-
- """
- return self.s + str(b)
diff --git a/pedantic/tests/tests_pedantic_python_311.py b/pedantic/tests/tests_pedantic_python_311.py
deleted file mode 100644
index c21116f2..00000000
--- a/pedantic/tests/tests_pedantic_python_311.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import unittest
-
-from pedantic import pedantic, pedantic_class
-from pedantic.exceptions import PedanticTypeCheckException
-
-
-class TestPedanticPython311AddedStuff(unittest.TestCase):
- def test_typing_never(self):
- from typing import Never
-
- @pedantic
- def never_call_me(arg: Never) -> None:
- pass
-
- @pedantic
- def foo() -> Never:
- pass
-
- @pedantic
- def bar() -> Never:
- raise ZeroDivisionError('bar')
-
- with self.assertRaises(expected_exception=ZeroDivisionError):
- bar()
-
- with self.assertRaises(PedanticTypeCheckException):
- foo()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException) as exc:
- never_call_me(arg='42')
-
- def test_literal_string(self):
- from typing import LiteralString
-
- @pedantic
- def foo(s: LiteralString) -> None:
- pass
-
- foo(s='Hi')
- foo(s=2 * 'Hi')
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- foo(s=3)
-
- def test_self_type(self):
- from typing import Self
-
- class Bar:
- pass
-
- @pedantic_class
- class Foo:
- def f(self) -> Self:
- return self
-
- @staticmethod
- def g() -> Self:
- return Foo()
-
- @classmethod
- def h(cls) -> Self:
- return cls()
-
- def f_2(self) -> Self:
- return Bar()
-
- @staticmethod
- def g_2() -> Self:
- return Bar()
-
- @classmethod
- def h_2(cls) -> Self:
- return Bar()
-
- f = Foo()
- assert f.f() == f
- f.g()
- f.h()
- Foo.g()
- Foo.h()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f.f_2()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f.g_2()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f.h_2()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo.g_2()
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- Foo.h_2()
-
- def test_using_self_type_annotation_outside_class(self):
- from typing import Self
-
- @pedantic
- def f() -> Self:
- return 'hi'
-
- with self.assertRaises(expected_exception=PedanticTypeCheckException):
- f()
-
- def test_type_var_tuple(self):
- from typing import TypeVarTuple, Generic
-
- Ts = TypeVarTuple('Ts')
-
- @pedantic_class
- class Array(Generic[*Ts]):
- def __init__(self, *args: *Ts) -> None:
- self._values = args
-
- @pedantic
- def add_dimension(a: Array[*Ts], value: int) -> Array[int, *Ts]:
- return Array[int, *Ts](value, *a._values)
-
- array = Array[int, float](42, 3.4)
- array_2 = Array[bool, int, float, str](True, 4, 3.4, 'hi')
- extended_array = add_dimension(a=array, value=42)
- assert extended_array._values == (42, 42, 3.4)
-
- # this is too complicated at the moment
- # with self.assertRaises(expected_exception=PedanticTypeCheckException):
- # Array[int, float](4.2, 3.4)
\ No newline at end of file
diff --git a/pedantic/tests/tests_require_kwargs.py b/pedantic/tests/tests_require_kwargs.py
deleted file mode 100644
index 24d8f20c..00000000
--- a/pedantic/tests/tests_require_kwargs.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import unittest
-
-# local file imports
-from pedantic.exceptions import PedanticCallWithArgsException
-from pedantic import require_kwargs
-
-
-class TestRequireKwargs(unittest.TestCase):
-
- def test_kwargs(self):
- @require_kwargs
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- calc(n=1, m=2, i=3)
-
- def test_no_kwargs_1(self):
- @require_kwargs
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- calc(1, m=2, i=3)
-
- def test_no_kwargs_2(self):
- @require_kwargs
- def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- calc(1, 2, 3)
diff --git a/pedantic/tests/tests_small_method_decorators.py b/pedantic/tests/tests_small_method_decorators.py
deleted file mode 100644
index c547d5c6..00000000
--- a/pedantic/tests/tests_small_method_decorators.py
+++ /dev/null
@@ -1,276 +0,0 @@
-import asyncio
-import unittest
-import warnings
-from abc import abstractmethod
-from unittest import IsolatedAsyncioTestCase
-
-from pedantic import overrides, timer, count_calls, trace, trace_if_returns, does_same_as_function, deprecated, \
- unimplemented, mock, require_kwargs
-from pedantic.exceptions import NotImplementedException, PedanticOverrideException, PedanticCallWithArgsException
-
-
-class TestSmallDecoratorMethods(unittest.TestCase):
- def test_overrides_parent_has_no_such_method(self):
- class MyClassA:
- pass
-
- with self.assertRaises(expected_exception=PedanticOverrideException):
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- def operation(self): pass
-
- def test_overrides_all_good(self):
- class MyClassA:
- def operation(self): pass
-
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- def operation(self):
- return 42
-
- b = MyClassB()
- b.operation()
-
- def test_overrides_static_method(self):
- class MyClassA:
- @staticmethod
- def operation(): pass
-
- class MyClassB(MyClassA):
- @staticmethod
- @overrides(MyClassA)
- def operation():
- return 42
-
- b = MyClassB()
- self.assertEqual(b.operation(), 42)
- self.assertEqual(MyClassB.operation(), 42)
-
- def test_overrides_below_property(self):
- class MyClassA:
- @property
- @abstractmethod
- def operation(self): pass
-
- class MyClassB(MyClassA):
- @property
- @overrides(MyClassA) # Note: it does not work the other way around
- def operation(self):
- return 43
-
- b = MyClassB()
- self.assertEqual(b.operation, 43)
-
- def test_overrides_function(self):
- class MyClassA:
- pass
-
- with self.assertRaises(expected_exception=PedanticOverrideException):
- @overrides(MyClassA)
- def operation(): return 42
-
- def test_deprecated_1(self):
- @deprecated
- def old_method(i: int) -> str: return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- old_method(42)
- assert len(w) == 1
- assert issubclass(w[-1].category, DeprecationWarning)
- assert "deprecated" in str(w[-1].message)
-
- def test_deprecated_2(self):
- def old_method(i: int) -> str:
- return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- old_method(42)
- assert not len(w) == 1
-
- def test_unimplemented(self):
- @unimplemented
- def dirt(i: int) -> str:
- return str(i)
-
- with self.assertRaises(expected_exception=NotImplementedException):
- dirt(42)
-
- def test_timer(self):
- @timer
- def operation(i: int) -> str:
- return str(i)
-
- operation(42)
-
- def test_count_calls(self):
- @count_calls
- def operation(i: int) -> str:
- return str(i)
-
- operation(42)
-
- def test_trace(self):
- def some_method(x, y):
- return x + y
-
- traced_method = trace(some_method)
- self.assertEqual(some_method(42, 99), traced_method(42, 99))
-
- def test_trace_if_returns(self):
- def some_method(x, y):
- return x + y
- traced_method = trace_if_returns(100)(some_method)
- self.assertEqual(some_method(42, 99), traced_method(42, 99))
- self.assertEqual(some_method(42, 58), traced_method(42, 58))
-
- def test_does_same_as_function(self):
- def some_method(x, y, z):
- return x * (y + z)
-
- @does_same_as_function(some_method)
- def other_method(x, y, z):
- return x * y + x * z
-
- other_method(1, 2, 3)
- other_method(4, 5, 6)
-
- def test_does_same_as_function_wrong(self):
- def some_method(x, y, z):
- return x * (y + z)
-
- @does_same_as_function(some_method)
- def other_method(x, y, z):
- return x * y + z
-
- other_method(0, 2, 0)
- with self.assertRaises(expected_exception=AssertionError):
- other_method(4, 5, 6)
-
-
-class AsyncSmallDecoratorTests(IsolatedAsyncioTestCase):
- async def test_overrides_async_instance_method(self) -> None:
- class MyClassA:
- async def operation(self): pass
-
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- async def operation(self):
- await asyncio.sleep(0)
- return 42
-
- b = MyClassB()
- await b.operation()
-
- async def test_overrides_parent_has_no_such_method_async(self):
- class MyClassA:
- pass
-
- with self.assertRaises(expected_exception=PedanticOverrideException):
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- async def operation(self): return 42
-
- async def test_count_calls_async(self):
- @count_calls
- async def operation(i: int) -> str:
- await asyncio.sleep(0)
- return str(i)
-
- res = await operation(42)
- self.assertEqual('42', res)
-
- async def test_trace_async(self):
- async def some_method(x, y):
- await asyncio.sleep(0)
- return x + y
-
- traced_method = trace(some_method)
- self.assertEqual(await some_method(42, 99), await traced_method(42, 99))
-
- async def test_trace_if_returns_async(self):
- async def some_method(x, y):
- await asyncio.sleep(0)
- return x + y
-
- traced_method = trace_if_returns(100)(some_method)
- self.assertEqual(await some_method(42, 99), await traced_method(42, 99))
- self.assertEqual(await some_method(42, 58), await traced_method(42, 58))
-
- async def test_timer_async(self):
- @timer
- async def operation(i: int) -> str:
- await asyncio.sleep(0.05)
- return str(i)
-
- await operation(42)
-
- async def test_deprecated_async(self):
- @deprecated
- async def old_method(i: int) -> str:
- return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- await old_method(42)
- assert len(w) == 1
- assert issubclass(w[-1].category, DeprecationWarning)
- assert "deprecated" in str(w[-1].message)
-
- async def test_does_same_as_function_async(self):
- async def some_method(x, y, z):
- await asyncio.sleep(0)
- return x * (y + z)
-
- @does_same_as_function(some_method)
- async def other_method(x, y, z):
- await asyncio.sleep(0)
- return x * y + x * z
-
- await other_method(1, 2, 3)
- await other_method(4, 5, 6)
-
- async def test_does_same_as_function_async_and_sync(self):
- def some_method(x, y, z):
- return x * (y + z)
-
- @does_same_as_function(some_method)
- async def other_method(x, y, z):
- await asyncio.sleep(0)
- return x * y + x * z
-
- await other_method(1, 2, 3)
- await other_method(4, 5, 6)
-
- async def test_does_same_as_function_wrong(self):
- async def some_method(x, y, z):
- await asyncio.sleep(0)
- return x * (y + z)
-
- @does_same_as_function(some_method)
- async def other_method(x, y, z):
- await asyncio.sleep(0)
- return x * y + z
-
- await other_method(0, 2, 0)
-
- with self.assertRaises(expected_exception=AssertionError):
- await other_method(4, 5, 6)
-
- async def test_mock_async(self) -> None:
- @mock(return_value=42)
- async def my_function(a, b, c): return a + b + c
-
- assert await my_function(1, 2, 3) == 42
- assert await my_function(100, 200, 300) == 42
-
- async def test_require_kwargs(self):
- @require_kwargs
- async def calc(n: int, m: int, i: int) -> int:
- return n + m + i
-
- await calc(n=1, m=2, i=3)
-
- with self.assertRaises(expected_exception=PedanticCallWithArgsException):
- await calc(1, m=2, i=3)
diff --git a/pedantic/tests/validate/test_convert_value.py b/pedantic/tests/validate/test_convert_value.py
deleted file mode 100644
index 3cc39173..00000000
--- a/pedantic/tests/validate/test_convert_value.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ConversionError
-from pedantic.decorators.fn_deco_validate.convert_value import convert_value
-
-
-class TestConvertValue(TestCase):
- def test_convert_to_bool(self):
- for value in [True, 1, '1', ' 1 ', ' tRuE ', 'TRUE']:
- self.assertTrue(convert_value(value=value, target_type=bool))
-
- for value in [False, 0, '0', ' 0 ', ' fAlSe ', 'FALSE']:
- self.assertFalse(convert_value(value=value, target_type=bool))
-
- for value in ['alse', 0.1, '0.2', ' 0000 ', 'Talse', 'Frue', 42]:
- with self.assertRaises(expected_exception=ConversionError):
- self.assertFalse(convert_value(value=value, target_type=bool))
-
- def test_convert_to_int(self):
- for value in range(-4, 4):
- self.assertEqual(value, convert_value(value=value, target_type=int))
-
- self.assertEqual(42, convert_value(value='42', target_type=int))
- self.assertEqual(0, convert_value(value=' 0000 ', target_type=int))
-
- for value in ['alse', 'Talse', 'Frue', 0.2, '0.2']:
- with self.assertRaises(expected_exception=ConversionError):
- self.assertFalse(convert_value(value=value, target_type=int))
-
- def test_convert_to_float(self):
- for value in range(-4, 4):
- self.assertEqual(value, convert_value(value=value, target_type=float))
-
- self.assertEqual(0.2, convert_value(value=0.2, target_type=float))
- self.assertEqual(0.2, convert_value(value='0.2', target_type=float))
- self.assertEqual(42, convert_value(value='42', target_type=float))
- self.assertEqual(0, convert_value(value=' 0000 ', target_type=float))
-
- for value in ['alse', 'Talse', 'Frue']:
- with self.assertRaises(expected_exception=ConversionError):
- self.assertFalse(convert_value(value=value, target_type=float))
-
- def test_convert_to_list(self):
- for value in [[], [1], ['1', ' 1 '], [' tRuE ', 'TRUE']]:
- self.assertEqual(value, convert_value(value=value, target_type=list))
-
- self.assertEqual(['1', '2', '3'], convert_value(value='1,2,3', target_type=list))
-
- def test_convert_to_dict(self):
- for value in [{}, {1: 2}, {'1': ' 1 '}, {1: ' tRuE ', 2: 'TRUE'}]:
- self.assertEqual(value, convert_value(value=value, target_type=dict))
-
- self.assertEqual({'1': '1', '2': '4', '3': '7'}, convert_value(value='1:1,2:4,3:7', target_type=dict))
-
-
-
diff --git a/pedantic/tests/validate/test_datetime_isoformat.py b/pedantic/tests/validate/test_datetime_isoformat.py
deleted file mode 100644
index 3c21a6e4..00000000
--- a/pedantic/tests/validate/test_datetime_isoformat.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from datetime import datetime, timedelta
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import DatetimeIsoFormat
-
-
-class TestValidatorDatetimeIsoformat(TestCase):
- def test_validator_datetime_isoformat(self) -> None:
- @validate(Parameter(name='x', validators=[DatetimeIsoFormat()]))
- def foo(x):
- return x
-
- now = datetime.now()
- self.assertTrue(abs((now - foo(now.isoformat()) < timedelta(milliseconds=1))))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo('12.12.2020')
-
- with self.assertRaises(expected_exception=ParameterException):
- foo('invalid')
diff --git a/pedantic/tests/validate/test_flask_parameters.py b/pedantic/tests/validate/test_flask_parameters.py
deleted file mode 100644
index 113be5c1..00000000
--- a/pedantic/tests/validate/test_flask_parameters.py
+++ /dev/null
@@ -1,609 +0,0 @@
-import json
-from dataclasses import dataclass
-from typing import List, Dict, Any
-from unittest import TestCase
-
-from flask import Flask, Response, jsonify
-
-from pedantic import Email, overrides
-from pedantic.decorators.fn_deco_validate.exceptions import ValidateException, TooManyArguments, \
- ParameterException, ExceptionDictKey, InvalidHeader
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, ReturnAs
-from pedantic.decorators.fn_deco_validate.parameters.flask_parameters import FlaskJsonParameter, FlaskFormParameter, \
- FlaskHeaderParameter, FlaskGetParameter, FlaskPathParameter, Deserializable, GenericFlaskDeserializer
-from pedantic.decorators.fn_deco_validate.validators import NotEmpty, Min
-
-JSON = 'application/json'
-OK = 200
-INVALID = 422
-TOO_MANY_ARGS = 400
-
-
-class TestFlaskParameters(TestCase):
- def test_validator_flask_json(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(
- FlaskJsonParameter(name='key', validators=[NotEmpty()]),
- )
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.route('/required')
- @validate(
- FlaskJsonParameter(name='required', required=True),
- FlaskJsonParameter(name='not_required', required=False),
- FlaskJsonParameter(name='not_required_with_default', required=False, default=42),
- )
- def required_params(required, not_required, not_required_with_default) -> Response:
- return jsonify({
- 'required': required,
- 'not_required': not_required,
- 'not_required_with_default': not_required_with_default,
- })
-
- @app.route('/types')
- @validate(
- FlaskJsonParameter(name='bool_param', value_type=bool),
- FlaskJsonParameter(name='int_param', value_type=int),
- FlaskJsonParameter(name='float_param', value_type=float),
- FlaskJsonParameter(name='str_param', value_type=str),
- FlaskJsonParameter(name='list_param', value_type=list),
- FlaskJsonParameter(name='dict_param', value_type=dict),
- )
- def different_types(
- bool_param,
- int_param,
- float_param,
- str_param,
- list_param,
- dict_param,
- ) -> Response:
- return jsonify({
- 'bool_param': bool_param,
- 'int_param': int_param,
- 'float_param': float_param,
- 'str_param': str_param,
- 'list_param': list_param,
- 'dict_param': dict_param,
- })
-
- @app.route('/args')
- @validate(
- FlaskJsonParameter(name='a', validators=[NotEmpty()]),
- FlaskJsonParameter(name='b', validators=[NotEmpty()]),
- return_as=ReturnAs.ARGS,
- )
- def names_do_not_need_to_match(my_key: str, another: str) -> Response:
- return jsonify({
- 'my_key': my_key,
- 'another': another,
- })
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- @app.errorhandler(TooManyArguments)
- def handle_validation_error(exception: TooManyArguments) -> Response:
- print(str(exception))
- response = jsonify(str(exception))
- response.status_code = TOO_MANY_ARGS
- return response
-
- with app.test_client() as client:
- res = client.get('/', data=json.dumps({'key': ' hello world '}), content_type=JSON)
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/', data=json.dumps({'key': ' '}), content_type=JSON)
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: 'NotEmpty',
- ExceptionDictKey.VALUE: ' ',
- ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- data = {
- 'key': ' hello world ',
- 'required': '1',
- }
- res = client.get('/', data=json.dumps(data), content_type=JSON)
- self.assertEqual(TOO_MANY_ARGS, res.status_code)
- self.assertEqual("Got unexpected arguments: ['required']", res.json)
-
- def test_validator_flask_form_data(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='key', validators=[NotEmpty()]))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/', data={'key': ' hello world '})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/', data={'key': ' '})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: 'NotEmpty',
- ExceptionDictKey.VALUE: ' ',
- ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- def test_validator_flask_header(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskHeaderParameter(name='key', validators=[NotEmpty()]))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(InvalidHeader)
- def handle_validation_error(exception: InvalidHeader) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/', headers={'key': ' hello world '}, data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/', headers={'key': ' '}, data={})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALUE: ' ',
- ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
- ExceptionDictKey.PARAMETER: 'key',
- ExceptionDictKey.VALIDATOR: 'NotEmpty',
- }
- self.assertEqual(expected, res.json)
-
- def test_validator_flask_header_optional_parameter(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskHeaderParameter(name='key', validators=[NotEmpty()], required=False))
- def hello_world(key: str = None) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get('/', headers={'key': ' hello world '}, data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/', headers={}, data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual(None, res.json)
-
- def test_validator_flask_get(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskGetParameter(name='key', value_type=str, validators=[NotEmpty()]))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/?key=hello_world', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello_world', res.json)
-
- res = client.get('/?key=hello world', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/?key= ', data={})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: 'NotEmpty',
- ExceptionDictKey.VALUE: ' ',
- ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- def test_validator_flask_get_multiple_values_for_same_key(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskGetParameter(name='key', value_type=list, validators=[NotEmpty()]))
- def hello_world(key: List[str]) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get('/?key=hello&key=world', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual(['hello', 'world'], res.json)
-
- def test_validator_flask_path(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskPathParameter(name='key', validators=[NotEmpty()]))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/hello_world', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello_world', res.json)
-
- res = client.get('/hello world', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('hello world', res.json)
-
- res = client.get('/ ', data={})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: 'NotEmpty',
- ExceptionDictKey.VALUE: ' ',
- ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- def test_invalid_value_type(self) -> None:
- app = Flask(__name__)
-
- with self.assertRaises(expected_exception=AssertionError):
- @app.route('/')
- @validate(FlaskFormParameter(name='key', value_type=tuple))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- def test_wrong_name(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='k', value_type=str))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ValidateException)
- def handle_validation_error(exception: ValidateException) -> Response:
- print(str(exception))
- response = jsonify(str(exception))
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get(data={'key': 'k'})
- self.assertEqual(INVALID, res.status_code)
- self.assertIn('Value for parameter k is required.', res.json)
-
- def test_default_value(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='key', value_type=str, default='42'))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get(data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('42', res.json)
-
- def test_not_required_allows_none_kwargs_without_none(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='key', value_type=str, required=False),
- return_as=ReturnAs.KWARGS_WITHOUT_NONE)
- def hello_world(key: str = 'it works') -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get(data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('it works', res.json)
-
- def test_not_required_allows_none_kwargs_with_none(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='key', value_type=str, required=False, default=None),
- return_as=ReturnAs.KWARGS_WITH_NONE)
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get(data={'key': None})
- self.assertEqual(OK, res.status_code)
- self.assertEqual(None, res.json)
-
- def test_not_required_with_default(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskFormParameter(name='key', value_type=str, required=False, default='42'))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get(data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('42', res.json)
-
- def test_validator_flask_path_type_conversion(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskPathParameter(name='key', value_type=int, validators=[Min(42)]))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/42', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual(42, res.json)
-
- res = client.get('/42f', data={})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: None,
- ExceptionDictKey.VALUE: '42f',
- ExceptionDictKey.MESSAGE: "Value 42f cannot be converted to .",
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- def test_validator_flask_json_parameter_does_not_get_json(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskJsonParameter(name='key'))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- print(str(exception))
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get('/', data={})
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.VALIDATOR: None,
- ExceptionDictKey.VALUE: 'None',
- ExceptionDictKey.MESSAGE: 'Value for parameter key is required.',
- ExceptionDictKey.PARAMETER: 'key',
- }
- self.assertEqual(expected, res.json)
-
- def test_validator_flask_json_parameter_does_not_get_json_but_default(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskJsonParameter(name='key', default='42'))
- def hello_world(key: str) -> Response:
- return jsonify(key)
-
- with app.test_client() as client:
- res = client.get('/', data={})
- self.assertEqual(OK, res.status_code)
- self.assertEqual('42', res.json)
-
- def test_too_many_arguments(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskJsonParameter(name='k', value_type=str))
- def hello_world(**kwargs) -> Response:
- return jsonify(str(kwargs))
-
- @app.errorhandler(TooManyArguments)
- def handle_validation_error(exception: TooManyArguments) -> Response:
- print(str(exception))
- response = jsonify(str(exception))
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get(data=json.dumps({'k': 'k', 'a': 1}), content_type=JSON)
- self.assertEqual(INVALID, res.status_code)
- self.assertEqual("Got unexpected arguments: ['a']", res.json)
-
- def test_exception_for_required_parameter(self) -> None:
- app = Flask(__name__)
- key = 'PASSWORD'
-
- @app.route('/')
- @validate(FlaskJsonParameter(name=key, value_type=str))
- def hello_world(**kwargs) -> Response:
- return jsonify(str(kwargs))
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- reason = 'required' if 'required' in exception.message else 'invalid'
- response = jsonify({exception.parameter_name: [{'KEY': reason}]})
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get(data=json.dumps({}), content_type=JSON)
- self.assertEqual(INVALID, res.status_code)
- self.assertEqual({key: [{'KEY': 'required'}]}, res.json)
-
- def test_async_endpoints(self) -> None:
- app = Flask(__name__)
-
- @app.route('/')
- @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]))
- async def hello_world(k) -> Response:
- return jsonify(str(k))
-
- @app.route('/foo/')
- @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]), return_as=ReturnAs.ARGS)
- async def return_args(k) -> Response:
- return jsonify(str(k))
-
- @app.route('/bar/')
- @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]),
- return_as=ReturnAs.KWARGS_WITH_NONE)
- async def return_kwargs_with_none(k) -> Response:
- return jsonify(str(k))
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- res = client.get(path=f'/45')
- self.assertEqual(OK, res.status_code)
-
- res = client.get(path=f'/foo/45')
- self.assertEqual(OK, res.status_code)
-
- res = client.get(path=f'/bar/45')
- self.assertEqual(OK, res.status_code)
-
- res = client.get(path=f'/41')
- self.assertEqual(INVALID, res.status_code)
- expected = {
- ExceptionDictKey.MESSAGE: 'smaller then allowed: 41 is not >= 42',
- ExceptionDictKey.PARAMETER: 'k',
- ExceptionDictKey.VALIDATOR: 'Min',
- ExceptionDictKey.VALUE: '41',
- }
- self.assertEqual(expected, res.json)
-
- def test_json_parameter_with_default_value(self) -> None:
- app = Flask(__name__)
-
- @app.route('/testing/message/', methods=['POST'])
- @validate(
- FlaskPathParameter(name='email', value_type=str, validators=[Email()]),
- FlaskJsonParameter(name='content', value_type=str, default='this is a fake message', required=False),
- )
- def testing_send_system_message(email: str, content: str) -> Response:
- return jsonify({'email': email, 'content': content})
-
- with app.test_client() as client:
- res = client.post(path=f'/testing/message/adam@eva.de')
- self.assertEqual(OK, res.status_code)
- expected = {
- 'email': 'adam@eva.de',
- 'content': 'this is a fake message',
- }
- self.assertEqual(expected, res.json)
-
- res = client.post(path=f'/testing/message/adam@eva.de', json={'content': 'hello world'})
- self.assertEqual(OK, res.status_code)
- expected = {
- 'email': 'adam@eva.de',
- 'content': 'hello world',
- }
- self.assertEqual(expected, res.json)
-
- def test_generic_deserializer(self) -> None:
- @dataclass(frozen=True)
- class User(Deserializable):
- firstname: str
- lastname: str
- age: int
-
- @staticmethod
- @overrides(Deserializable)
- def from_json(data: Dict[str, Any]) -> 'User':
- return User(
- firstname=data['firstname'],
- lastname=data['lastname'],
- age=Min(value=18).validate_param(value=int(data['age']), parameter_name='age'),
- )
-
- app = Flask(__name__)
-
- @app.route('/foo', methods=['POST'])
- @validate(
- GenericFlaskDeserializer(name='user', cls=User, catch_exception=True),
- )
- def the_generic_approach(user: User) -> Response:
- return jsonify({'name': user.firstname})
-
- @app.route('/bar', methods=['POST'])
- @validate(
- GenericFlaskDeserializer(name='user', cls=User, catch_exception=False),
- )
- def do_not_catch(user: User) -> Response:
- return jsonify({'name': user.firstname})
-
- @app.errorhandler(ParameterException)
- def handle_validation_error(exception: ParameterException) -> Response:
- response = jsonify(exception.to_dict)
- response.status_code = INVALID
- return response
-
- with app.test_client() as client:
- data = {
- 'firstname': 'Albert',
- 'lastname': 'Einstein',
- 'age': '56',
- }
- res = client.post(path=f'/foo', json=data)
- assert res.status_code == 200
- assert res.json == {'name': 'Albert'}
-
- data.pop('age')
- res = client.post(path=f'/foo', json=data)
- assert res.status_code == 422
-
- res = client.post(path=f'/bar', json=data)
- assert res.status_code == 500
-
- data['age'] = '16'
- res = client.post(path=f'/foo', json=data)
- assert res.status_code == 422
- expected = {
- ExceptionDictKey.MESSAGE: 'smaller then allowed: 16 is not >= 18',
- ExceptionDictKey.PARAMETER: 'age',
- ExceptionDictKey.VALIDATOR: 'Min',
- ExceptionDictKey.VALUE: '16',
- }
- assert res.json == expected
diff --git a/pedantic/tests/validate/test_parameter_environment_variable.py b/pedantic/tests/validate/test_parameter_environment_variable.py
deleted file mode 100644
index a2404d84..00000000
--- a/pedantic/tests/validate/test_parameter_environment_variable.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import os
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import EnvironmentVariableParameter
-
-
-class TestParameterEnvironmentVariable(TestCase):
- def setUp(self) -> None:
- if 'foo' in os.environ:
- del os.environ['foo']
-
- def test_parameter_environment_variable_str(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo', value_type=str))
- def bar(foo):
- return foo
-
- os.environ['foo'] = '42'
- self.assertEqual('42', bar())
-
- def test_parameter_environment_variable_int(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo', value_type=int))
- def bar(foo):
- return foo
-
- os.environ['foo'] = '42'
- self.assertEqual(42, bar())
-
- def test_parameter_environment_variable_float(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo', value_type=float))
- def bar(foo):
- return foo
-
- os.environ['foo'] = '42.7'
- self.assertEqual(42.7, bar())
-
- def test_parameter_environment_variable_bool(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo', value_type=bool))
- def bar(foo):
- return foo
-
- for value in ['true', 'True', 'TRUE']:
- os.environ['foo'] = value
- self.assertTrue(bar())
-
- for value in ['false', 'False', 'FALSE']:
- os.environ['foo'] = value
- self.assertFalse(bar())
-
- for value in ['invalid', 'frue', 'talse']:
- os.environ['foo'] = value
-
- with self.assertRaises(expected_exception=ParameterException):
- bar()
-
- def test_parameter_environment_variable_not_set(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo'))
- def bar(foo):
- return foo
-
- with self.assertRaises(expected_exception=ParameterException):
- bar()
-
- def test_invalid_value_type(self) -> None:
- with self.assertRaises(expected_exception=AssertionError):
- @validate(EnvironmentVariableParameter(name='foo', value_type=dict))
- def bar(foo):
- return foo
-
- def test_parameter_environment_variable_different_name(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo', env_var_name='fuu', value_type=str))
- def bar(foo):
- return foo
-
- os.environ['fuu'] = '42'
- self.assertEqual('42', bar())
-
- def test_two_parameters(self) -> None:
- @validate(EnvironmentVariableParameter(name='a'), strict=False)
- def foo(a: float, b: int):
- print(f'{a} and {b}')
-
- os.environ['a'] = '42'
- foo(b=42)
diff --git a/pedantic/tests/validate/test_validate.py b/pedantic/tests/validate/test_validate.py
deleted file mode 100644
index da6b38f9..00000000
--- a/pedantic/tests/validate/test_validate.py
+++ /dev/null
@@ -1,428 +0,0 @@
-import os
-from datetime import datetime
-from typing import Optional, Any
-from unittest import IsolatedAsyncioTestCase
-from unittest import TestCase
-
-from pedantic import DateTimeUnixTimestamp
-from pedantic.decorators.fn_deco_validate.exceptions import ValidateException, ParameterException, \
- ValidatorException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, ReturnAs
-from pedantic.decorators.fn_deco_validate.parameters import Parameter, EnvironmentVariableParameter
-from pedantic.decorators.fn_deco_validate.validators import MaxLength, Min, Max, Email, Validator
-
-
-class TestValidate(TestCase):
- def setUp(self) -> None:
- if 'foo' in os.environ:
- del os.environ['foo']
-
- def test_single_validator(self) -> None:
- validator = MaxLength(3)
- converted_value = validator.validate(value='hed')
- self.assertEqual(converted_value, 'hed')
-
- with self.assertRaises(expected_exception=ValidatorException) as ex:
- validator.validate(value='hello world')
-
- expected_error_msg = 'MaxLength: hello world is too long with length 11. Value: hello world'
- self.assertEqual(expected_error_msg, str(ex.exception))
-
- def test_single_parameter(self) -> None:
- parameter = Parameter(name='x', validators=[MaxLength(3)])
- converted_value = parameter.validate(value='hed')
- self.assertEqual(converted_value, 'hed')
-
- with self.assertRaises(expected_exception=ParameterException):
- parameter.validate(value='hello world')
-
- def test_multiple_parameters(self) -> None:
- @validate(
- Parameter(name='a', validators=[Min(3)]),
- Parameter(name='b', validators=[Max(3)]),
- Parameter(name='c', validators=[Max(43)]),
- )
- def bar(a, b, c):
- return a + b + c
-
- self.assertEqual(11, bar(3, 3, 5))
- self.assertEqual(11, bar(a=3, b=3, c=5))
-
- def test_validate_args(self):
- @validate(
- Parameter(name='a', validators=[Min(42, include_boundary=False)]),
- Parameter(name='b', validators=[Min(42, include_boundary=False)]),
- Parameter(name='c', validators=[Min(42, include_boundary=False)]),
- )
- def some_calculation(a, b, c):
- return a + b + c
-
- some_calculation(43, 45, 50)
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(30, 40, 50)
- with self.assertRaises(expected_exception=ParameterException):
- some_calculation(c=30, a=40, b=50)
-
- def test_validate_instance_method(self):
- class MyClass:
- @validate(
- Parameter(name='x', validators=[Min(1)]),
- )
- def some_calculation(self, x: int) -> int:
- return x
-
- @validate(
- Parameter(name='x', validators=[Min(1)]),
- return_as=ReturnAs.KWARGS_WITHOUT_NONE,
- )
- def some_calculation_2(self, x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(1)
- m.some_calculation(42)
-
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(0)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(-42)
-
- m.some_calculation_2(1)
- m.some_calculation_2(42)
-
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation_2(0)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation_2(-42)
-
- def test_validate_static_method(self):
- """ The @staticmethod decorator have to be ABOVE the @validate decorator. """
-
- class MyClass:
- @staticmethod
- @validate(
- Parameter(name='x', validators=[Min(1)]),
- )
- def some_calculation(x: int) -> int:
- return x
-
- m = MyClass()
- m.some_calculation(1)
- m.some_calculation(42)
-
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(0)
- with self.assertRaises(expected_exception=ParameterException):
- m.some_calculation(-42)
-
- def test_less_parameter_than_arguments(self):
- @validate(
- Parameter(name='b'),
- strict=False,
- )
- def some_calculation(a, b, c):
- return a + b + c
-
- some_calculation(43, 0, -50)
-
- with self.assertRaises(expected_exception=ValidateException):
- some_calculation(30, None, 50)
-
- def test_empty_parameter_kwargs_with_none(self):
- @validate(
- Parameter(name='a', required=False),
- Parameter(name='b', required=True),
- Parameter(name='c', required=False),
- return_as=ReturnAs.KWARGS_WITH_NONE
- )
- def some_calculation(a, b, c):
- return str(a) + str(b) + str(c)
-
- self.assertEqual('430-50', some_calculation(43, 0, -50))
- self.assertEqual('None0None', some_calculation(None, 0, None))
-
- def test_empty_parameter_kwargs_without_none(self):
- @validate(
- Parameter(name='a', required=False),
- Parameter(name='b', required=True),
- Parameter(name='c', required=False),
- return_as=ReturnAs.KWARGS_WITHOUT_NONE
- )
- def some_calculation(a: Optional[int] = 1, b: Optional[int] = 2, c: Optional[int] = 3):
- return str(a) + str(b) + str(c)
-
- self.assertEqual('430-50', some_calculation(43, 0, -50))
- self.assertEqual('103', some_calculation(None, 0, None))
-
- def test_required(self):
- @validate(
- Parameter(name='a', required=True),
- Parameter(name='b', required=True),
- Parameter(name='c', required=True),
- )
- def some_calculation(a, b, c):
- return a + b + c
-
- some_calculation(43, 0, -50)
-
- with self.assertRaises(expected_exception=ValidateException):
- some_calculation(30, None, 50)
-
- def test_call_with_args(self):
- @validate(
- Parameter(name='x', validators=[Min(1)]),
- )
- def some_calculation(x: int) -> int:
- return x
-
- some_calculation(42)
-
- def test_external_parameter_accepts_value_when_given(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo'))
- def bar(foo):
- return foo
-
- self.assertEqual('42', bar('42'))
- self.assertEqual('42', bar(foo='42'))
-
- def test_external_parameter_ignores_value_when_given(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo'), ignore_input=True)
- def bar(foo):
- return foo
-
- os.environ['foo'] = '1'
-
- self.assertEqual('1', bar('42'))
- self.assertEqual('1', bar(foo='42'))
-
- def test_external_parameter_mixed_with_normal_parameter(self) -> None:
- @validate(
- EnvironmentVariableParameter(name='foo'),
- Parameter(name='footer'),
- return_as=ReturnAs.KWARGS_WITHOUT_NONE,
- )
- def bar(foo, footer):
- return foo, footer
-
- self.assertEqual(('42', 3), bar('42', 3))
-
- os.environ['foo'] = '42'
- self.assertEqual(('42', 3), bar(footer=3))
-
- def test_too_many_arguments(self) -> None:
- @validate(
- Parameter(name='x'),
- )
- def bar(x):
- return x
-
- self.assertEqual(42, bar(42))
-
- with self.assertRaises(expected_exception=ValidateException):
- bar(42, 43)
-
- def test_unexpected_parameter_strict(self) -> None:
- @validate(Parameter(name='y'))
- def bar(x):
- return x
-
- with self.assertRaises(expected_exception=ValidateException):
- bar(42)
- with self.assertRaises(expected_exception=ValidateException):
- bar(x=42)
-
- def test_unexpected_parameter_not_strict(self) -> None:
- @validate(Parameter(name='y'), strict=False)
- def bar(x):
- return x
-
- with self.assertRaises(expected_exception=ValidateException):
- self.assertEqual(42, bar(42))
-
- with self.assertRaises(expected_exception=ValidateException):
- self.assertEqual(42, bar(x=42))
-
- def test_unexpected_parameter_not_strict_external(self) -> None:
- @validate(EnvironmentVariableParameter(name='foo'))
- def bar(x):
- return x
-
- with self.assertRaises(expected_exception=ValidateException):
- self.assertEqual(42, bar(42))
-
- with self.assertRaises(expected_exception=ValidateException):
- self.assertEqual(42, bar(x=42))
-
- def test_return_as_simple(self) -> None:
- @validate(Parameter(name='x'), return_as=ReturnAs.ARGS)
- def bar(x):
- return x
-
- self.assertEqual(42, bar(42))
- self.assertEqual(42, bar(x=42))
-
- def test_return_as_args(self) -> None:
- @validate(Parameter(name='x'), return_as=ReturnAs.ARGS)
- def bar(*args, **kwargs):
- return args, kwargs
-
- self.assertEqual(((42,), {}), bar(42))
- self.assertEqual(((42,), {}), bar(x=42))
-
- def test_return_as_kwargs_with_none(self) -> None:
- @validate(Parameter(name='x'), return_as=ReturnAs.KWARGS_WITH_NONE)
- def bar(*args, **kwargs):
- return args, kwargs
-
- self.assertEqual(((), {'x': 42}), bar(42))
- self.assertEqual(((), {'x': 42}), bar(x=42))
-
- def test_return_as_kwargs_without_none(self) -> None:
- @validate(Parameter(name='x'), return_as=ReturnAs.KWARGS_WITHOUT_NONE)
- def bar(*args, **kwargs):
- return args, kwargs
-
- self.assertEqual(((), {'x': 42}), bar(42))
- self.assertEqual(((), {'x': 42}), bar(x=42))
-
- def test_return_as_args_advanced(self) -> None:
- @validate(
- Parameter(name='a'),
- Parameter(name='b'),
- Parameter(name='c'),
- return_as=ReturnAs.ARGS,
- )
- def bar(a, b, *args, **kwargs):
- return a, b, args, kwargs
-
- bar(a=1, b=3, c=42)
- bar(1, 3, 4)
- bar(1, 3, c=4)
-
- def test_return_as_args_advanced_different_order(self) -> None:
- @validate(
- Parameter(name='c'),
- Parameter(name='a'),
- Parameter(name='b'),
- return_as=ReturnAs.ARGS,
- )
- def bar(a, b, *args, **kwargs):
- return a, b, args, kwargs
-
- self.assertEqual((1, 3, (42,), {}), bar(a=1, b=3, c=42))
- self.assertEqual((1, 3, (42,), {}), bar(1, 3, 42))
- self.assertEqual((42, 1, (3,), {}), bar(1, 3, c=42))
-
- def test_return_multiple_args(self) -> None:
- @validate(
- Parameter(name='c'),
- Parameter(name='a'),
- Parameter(name='b'),
- return_as=ReturnAs.KWARGS_WITH_NONE,
- )
- def bar(*args, **kwargs):
- return args, kwargs
-
- self.assertEqual(((), {'a': 1, 'b': 3, 'c': 42}), bar(a=1, b=3, c=42))
- self.assertEqual(((), {'a': 3, 'b': 42, 'c': 1}), bar(1, 3, 42))
- self.assertEqual(((), {'a': 1, 'b': 3, 'c': 42}), bar(1, 3, c=42))
-
- def test_none_is_not_validated_if_not_required_kwargs_with_none(self) -> None:
- @validate(Parameter(name='a', validators=[Email()], required=False), return_as=ReturnAs.KWARGS_WITH_NONE)
- def bar(a: Optional[str]):
- return a
-
- self.assertIsNone(bar(a=None))
- self.assertIsNone(bar(None))
-
- with self.assertRaises(expected_exception=ParameterException):
- bar('no_email')
-
- def test_none_is_not_validated_if_not_required_kwargs_without_none(self) -> None:
- @validate(Parameter(name='a', validators=[Email()], required=False), return_as=ReturnAs.KWARGS_WITHOUT_NONE)
- def bar(a: Optional[str] = None):
- return a
-
- self.assertIsNone(bar(a=None))
- self.assertIsNone(bar(None))
-
- with self.assertRaises(expected_exception=ParameterException):
- bar('no_email')
-
- def test_allow_renaming_of_parameter_of_custom_validator(self) -> None:
- class MyCustomValidator(Validator):
- def validate(self, i_renamed_this_arg: Any) -> Any:
- return i_renamed_this_arg
-
- @validate(Parameter(name='a', validators=[MyCustomValidator()]))
- def bar(a: int):
- return a
-
- self.assertEqual(42, bar(42))
- self.assertEqual(42, bar(a=42))
-
- def test_none_is_removed_for_not_required_parameter(self) -> None:
- @validate(Parameter(name='a', required=False))
- def bar(a: int = 42):
- return a
-
- self.assertEqual(42, bar())
- self.assertEqual(2, bar(a=2))
- self.assertEqual(2, bar(2))
-
- def test_default_value_is_not_validated_internal_parameter(self) -> None:
- t = datetime(year=2021, month=11, day=24)
- unix_timestamp = (t - datetime(year=1970, month=1, day=1)).total_seconds()
-
- @validate(Parameter(name='a', required=False, default=t, validators=[DateTimeUnixTimestamp()]))
- def bar(a: datetime) -> datetime:
- return a
-
- self.assertEqual(t, bar(a=unix_timestamp))
- self.assertEqual(t, bar())
-
- def test_no_default_value(self) -> None:
- @validate(Parameter(name='a', required=False))
- def bar(a: datetime) -> datetime:
- return a
-
- with self.assertRaises(expected_exception=ValidateException):
- bar()
-
- def test_default_value_is_not_validated_external_parameter(self) -> None:
- t = datetime(year=2021, month=11, day=24)
-
- if 'a' in os.environ:
- del os.environ['a']
-
- @validate(EnvironmentVariableParameter(name='a', default=t, validators=[DateTimeUnixTimestamp()], required=False))
- def bar(a: datetime) -> datetime:
- return a
-
- self.assertEqual(t, bar())
-
-
-class AsyncValidateTests(IsolatedAsyncioTestCase):
- async def test_async_instance_method(self) -> None:
- class Foo:
- @validate(Parameter(name='k', value_type=int, validators=[Min(42)]),
- return_as=ReturnAs.KWARGS_WITHOUT_NONE)
- async def bar(self, k):
- return k
-
- @validate(Parameter(name='k', value_type=int, validators=[Min(42)]), return_as=ReturnAs.ARGS)
- async def bar_2(self, k):
- return k
-
- f = Foo()
- res = await f.bar(k=42)
- self.assertEqual(42, res)
-
- with self.assertRaises(expected_exception=ParameterException):
- await f.bar(k=41)
-
- res = await f.bar_2(k=42)
- self.assertEqual(42, res)
-
- with self.assertRaises(expected_exception=ParameterException):
- await f.bar_2(k=41)
diff --git a/pedantic/tests/validate/test_validator_composite.py b/pedantic/tests/validate/test_validator_composite.py
deleted file mode 100644
index 7e5c0a6e..00000000
--- a/pedantic/tests/validate/test_validator_composite.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import Composite, Max, Min
-
-
-class TestValidatorComposite(TestCase):
- def test_validator_composite(self) -> None:
- @validate(Parameter(name='x', validators=[Composite([Min(3), Max(5)])]))
- def foo(x):
- return x
-
- self.assertEqual(3, foo(3))
- self.assertEqual(4, foo(4))
- self.assertEqual(5, foo(5))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(5.0001)
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(2.9999)
diff --git a/pedantic/tests/validate/test_validator_datetime_unix_timestamp.py b/pedantic/tests/validate/test_validator_datetime_unix_timestamp.py
deleted file mode 100644
index 82ba6f75..00000000
--- a/pedantic/tests/validate/test_validator_datetime_unix_timestamp.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from datetime import datetime
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import DateTimeUnixTimestamp
-
-
-class TestValidatorDatetimeUnixTimestamp(TestCase):
- def test_validator_datetime_unix_timestamp(self) -> None:
- @validate(Parameter(name='x', validators=[DateTimeUnixTimestamp()]))
- def foo(x):
- return x
-
- now = datetime.now()
- unix_timestamp = (now - datetime(year=1970, month=1, day=1)).total_seconds()
- self.assertEqual(now, foo(unix_timestamp))
- self.assertEqual(now, foo(str(unix_timestamp)))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo('12.12.2020')
-
- with self.assertRaises(expected_exception=ParameterException):
- foo('invalid')
-
- with self.assertRaises(expected_exception=ParameterException):
- foo({'a': 1})
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(unix_timestamp * 1000)
diff --git a/pedantic/tests/validate/test_validator_email.py b/pedantic/tests/validate/test_validator_email.py
deleted file mode 100644
index 71bc22e0..00000000
--- a/pedantic/tests/validate/test_validator_email.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import Email
-
-
-class TestValidatorEmail(TestCase):
- def test_validator_email(self) -> None:
- @validate(Parameter(name='x', validators=[Email()]))
- def foo(x):
- return x
-
- for value in ['fred@web.de', 'genial@gmail.com', 'test@test.co.uk']:
- self.assertEqual(value, foo(value))
-
- for value in ['fred', 'fred@web', 'fred@w@eb.de', 'fred@@web.de', 'invalid@invalid']:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
-
- def test_validator_email_converts_to_lower_case(self) -> None:
- @validate(Parameter(name='x', validators=[Email(post_processor=lambda x: x.lower())]))
- def foo(x):
- return x
-
- for value in ['Fred@Web.de', 'GENIAL@GMAIL.com', 'test@test.CO.UK']:
- self.assertEqual(value.lower(), foo(value))
diff --git a/pedantic/tests/validate/test_validator_for_each.py b/pedantic/tests/validate/test_validator_for_each.py
deleted file mode 100644
index 80532add..00000000
--- a/pedantic/tests/validate/test_validator_for_each.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import ForEach, Min, Max
-
-
-class TestValidatorForEach(TestCase):
- def test_validator_for_each_single_child(self) -> None:
- @validate(Parameter(name='x', validators=[ForEach(Min(3))]))
- def foo(x):
- return x
-
- self.assertEqual([3, 4, 5], foo([3, 4, 5]))
-
- for value in [42, [3, 2, 5]]:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
-
- def test_validator_for_each_multiple_children(self) -> None:
- @validate(Parameter(name='x', validators=[ForEach([Min(3), Max(4)])]))
- def foo(x):
- return x
-
- self.assertEqual([3, 4], foo([3, 4]))
-
- for value in [42, [3, 2, 5]]:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
diff --git a/pedantic/tests/validate/test_validator_is_enum.py b/pedantic/tests/validate/test_validator_is_enum.py
deleted file mode 100644
index e496b564..00000000
--- a/pedantic/tests/validate/test_validator_is_enum.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from enum import Enum, IntEnum
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import IsEnum
-
-
-class MyEnum(Enum):
- RED = 'RED'
- BLUE = 'BLUE'
-
-
-class MyIntEnum(IntEnum):
- RED = 1
- BLUE = 2
-
-
-class TestValidatorIsEnum(TestCase):
- def test_validator_is_enum_convert_true(self) -> None:
- @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=True)]))
- def foo(x):
- return x
-
- self.assertEqual(MyEnum.RED, foo('RED'))
- self.assertEqual(MyEnum.BLUE, foo('BLUE'))
-
- for value in ['fred', 1, 'GREEN']:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
-
- def test_validator_is_enum_int_enum_convert_true(self) -> None:
- @validate(Parameter(name='x', validators=[IsEnum(MyIntEnum, convert=True)]))
- def foo(x):
- return x
-
- self.assertEqual(MyIntEnum.RED, foo('1'))
- self.assertEqual(MyIntEnum.BLUE, foo('2'))
- self.assertEqual(MyIntEnum.RED, foo(1))
- self.assertEqual(MyIntEnum.BLUE, foo(2))
-
- for value in ['fred', 3, 'GREEN']:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
-
- def test_validator_is_enum_convert_false(self) -> None:
- @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False)]))
- def foo(x):
- return x
-
- self.assertEqual('RED', foo('RED'))
- self.assertEqual('BLUE', foo('BLUE'))
-
- for value in ['fred', 1, 'GREEN']:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
-
- def test_validator_is_enum_to_upper_case(self) -> None:
- @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False)]))
- def foo(x):
- return x
-
- self.assertEqual('RED', foo('red'))
- self.assertEqual('BLUE', foo('blue'))
- self.assertEqual('BLUE', foo('bLUe'))
-
- def test_validator_is_enum_to_upper_case_disabled(self) -> None:
- @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False, to_upper_case=False)]))
- def foo(x): print(x)
-
- for value in ['red', 'blue', 'Red', 'bLUe']:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
diff --git a/pedantic/tests/validate/test_validator_is_uuid.py b/pedantic/tests/validate/test_validator_is_uuid.py
deleted file mode 100644
index 2bc07118..00000000
--- a/pedantic/tests/validate/test_validator_is_uuid.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from unittest import TestCase
-from uuid import uuid1, uuid3, uuid4, uuid5
-
-from pedantic import ForEach
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import IsUuid
-
-
-class TestValidatorIsUUID(TestCase):
- def test_validator_is_uuid(self):
- @validate(Parameter(name='x', validators=[IsUuid()], required=False))
- def foo(x):
- return x
-
- for id_ in [str(uuid1()), str(uuid3(uuid1(), 'b')), str(uuid4()), str(uuid5(uuid1(), 'b'))]:
- self.assertEqual(id_, foo(id_))
-
- for no_id in ['invalid', 12]:
- with self.assertRaises(expected_exception=ParameterException):
- foo(no_id)
-
- def test_validator_is_uuid_with_for_each_and_none_value(self):
- @validate(Parameter(name='x', validators=[ForEach(IsUuid())]))
- def foo(x):
- return x
-
- uuid = str(uuid1())
- self.assertEqual([], foo([]))
- self.assertEqual([uuid], foo([uuid]))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo([None])
diff --git a/pedantic/tests/validate/test_validator_match_pattern.py b/pedantic/tests/validate/test_validator_match_pattern.py
deleted file mode 100644
index 1cc310d2..00000000
--- a/pedantic/tests/validate/test_validator_match_pattern.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import MatchPattern
-
-
-class TestValidatorMatchPattern(TestCase):
- def test_validator_match_pattern(self) -> None:
- pattern = r'^(([01]\d|2[0-3]):([0-5]\d)|24:00)$'
-
- @validate(Parameter(name='x', validators=[MatchPattern(pattern)]))
- def foo(x):
- return x
-
- for value in ['00:00', '02:45', '14:59', '23:59']:
- self.assertEqual(value, foo(value))
-
- for value in ['00:70', '24:00', '30:00', 'invalid']:
- with self.assertRaises(expected_exception=ParameterException):
- foo([3, 2, 5])
diff --git a/pedantic/tests/validate/test_validator_max.py b/pedantic/tests/validate/test_validator_max.py
deleted file mode 100644
index 08f74b48..00000000
--- a/pedantic/tests/validate/test_validator_max.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators.max import Max
-
-
-class TestValidatorMax(TestCase):
- def test_validator_max_length_include_boundary_true(self) -> None:
- @validate(Parameter(name='x', validators=[Max(3, include_boundary=True)]))
- def foo(x):
- return x
-
- self.assertEqual(3, foo(3))
- self.assertEqual(2, foo(2))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(4)
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(3.001)
-
- def test_validator_max_length_include_boundary_false(self) -> None:
- @validate(Parameter(name='x', validators=[Max(3, include_boundary=False)]))
- def foo(x):
- return x
-
- self.assertEqual(2.9999, foo(2.9999))
- self.assertEqual(2, foo(2))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(4)
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(3)
diff --git a/pedantic/tests/validate/test_validator_max_length.py b/pedantic/tests/validate/test_validator_max_length.py
deleted file mode 100644
index b56281cb..00000000
--- a/pedantic/tests/validate/test_validator_max_length.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import MaxLength
-
-
-class TestValidatorMaxLength(TestCase):
- def test_validator_max_length(self) -> None:
- @validate(Parameter(name='x', validators=[MaxLength(3)]))
- def foo(x):
- return x
-
- self.assertEqual('hi', foo('hi'))
- self.assertEqual('hi!', foo('hi!'))
- self.assertEqual([1, 2, 3], foo([1, 2, 3]))
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo('hi!!')
-
- assert ex.exception.message == 'hi!! is too long with length 4.'
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo([1, 2, 3, 4])
-
- assert ex.exception.message == '[1, 2, 3, 4] is too long with length 4.'
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo(42)
-
- assert ex.exception.message == '42 has no length.'
diff --git a/pedantic/tests/validate/test_validator_min.py b/pedantic/tests/validate/test_validator_min.py
deleted file mode 100644
index a97d0f8b..00000000
--- a/pedantic/tests/validate/test_validator_min.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import Min
-
-
-class TestValidatorMin(TestCase):
- def test_validator_min_length_include_boundary_true(self) -> None:
- @validate(Parameter(name='x', validators=[Min(3, include_boundary=True)]))
- def foo(x):
- return x
-
- self.assertEqual(3, foo(3))
- self.assertEqual(4, foo(4))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(2)
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(2.9999)
-
- def test_validator_min_length_include_boundary_false(self) -> None:
- @validate(Parameter(name='x', validators=[Min(3, include_boundary=False)]))
- def foo(x):
- return x
-
- self.assertEqual(3.0001, foo(3.0001))
- self.assertEqual(4, foo(4))
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(2)
-
- with self.assertRaises(expected_exception=ParameterException):
- foo(3)
diff --git a/pedantic/tests/validate/test_validator_min_length.py b/pedantic/tests/validate/test_validator_min_length.py
deleted file mode 100644
index d25f5e3c..00000000
--- a/pedantic/tests/validate/test_validator_min_length.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import MinLength
-
-
-class TestValidatorMinLength(TestCase):
- def test_validator_min_length(self) -> None:
- @validate(Parameter(name='x', validators=[MinLength(3)]))
- def foo(x):
- return x
-
- self.assertEqual('hi!', foo('hi!'))
- self.assertEqual('hello', foo('hello'))
- self.assertEqual([1, 2, 3], foo([1, 2, 3]))
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo('hi')
-
- assert ex.exception.message == 'hi is too short with length 2.'
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo([1, 2])
-
- assert ex.exception.message == '[1, 2] is too short with length 2.'
-
- with self.assertRaises(expected_exception=ParameterException) as ex:
- foo(42)
-
- assert ex.exception.message == '42 has no length.'
diff --git a/pedantic/tests/validate/test_validator_not_empty.py b/pedantic/tests/validate/test_validator_not_empty.py
deleted file mode 100644
index 0ba3d493..00000000
--- a/pedantic/tests/validate/test_validator_not_empty.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from unittest import TestCase
-
-from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
-from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
-from pedantic.decorators.fn_deco_validate.parameters import Parameter
-from pedantic.decorators.fn_deco_validate.validators import NotEmpty
-
-
-class TestValidatorNotEmpty(TestCase):
- def test_validator_not_empty(self) -> None:
- @validate(Parameter(name='x', validators=[NotEmpty()]))
- def foo(x):
- return x
-
- self.assertEqual('hi', foo('hi'))
- self.assertEqual('hi', foo(' hi '))
- self.assertEqual([1], foo([1]))
-
- for value in ['', ' ', [], {}, set()]:
- with self.assertRaises(expected_exception=ParameterException):
- foo(value)
diff --git a/poetry.lock b/poetry.lock
index ceda8982..cb350437 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -52,12 +52,132 @@ description = "Cross-platform colored terminal text."
optional = true
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"]
-markers = "extra == \"dev\" and platform_system == \"Windows\""
+markers = "extra == \"dev\" and (sys_platform == \"win32\" or platform_system == \"Windows\")"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+[[package]]
+name = "coverage"
+version = "7.13.4"
+description = "Code coverage measurement for Python"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415"},
+ {file = "coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b"},
+ {file = "coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a"},
+ {file = "coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f"},
+ {file = "coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012"},
+ {file = "coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def"},
+ {file = "coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256"},
+ {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda"},
+ {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92"},
+ {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c"},
+ {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58"},
+ {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9"},
+ {file = "coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf"},
+ {file = "coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95"},
+ {file = "coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053"},
+ {file = "coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11"},
+ {file = "coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa"},
+ {file = "coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7"},
+ {file = "coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00"},
+ {file = "coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef"},
+ {file = "coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903"},
+ {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f"},
+ {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299"},
+ {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505"},
+ {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6"},
+ {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9"},
+ {file = "coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9"},
+ {file = "coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f"},
+ {file = "coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f"},
+ {file = "coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459"},
+ {file = "coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3"},
+ {file = "coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634"},
+ {file = "coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3"},
+ {file = "coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa"},
+ {file = "coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3"},
+ {file = "coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a"},
+ {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7"},
+ {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc"},
+ {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47"},
+ {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985"},
+ {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0"},
+ {file = "coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246"},
+ {file = "coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126"},
+ {file = "coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d"},
+ {file = "coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9"},
+ {file = "coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac"},
+ {file = "coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea"},
+ {file = "coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b"},
+ {file = "coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525"},
+ {file = "coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242"},
+ {file = "coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148"},
+ {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a"},
+ {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23"},
+ {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80"},
+ {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea"},
+ {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a"},
+ {file = "coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d"},
+ {file = "coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd"},
+ {file = "coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af"},
+ {file = "coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d"},
+ {file = "coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12"},
+ {file = "coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b"},
+ {file = "coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9"},
+ {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092"},
+ {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9"},
+ {file = "coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26"},
+ {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2"},
+ {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940"},
+ {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c"},
+ {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0"},
+ {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b"},
+ {file = "coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9"},
+ {file = "coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd"},
+ {file = "coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997"},
+ {file = "coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601"},
+ {file = "coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689"},
+ {file = "coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c"},
+ {file = "coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129"},
+ {file = "coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552"},
+ {file = "coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a"},
+ {file = "coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356"},
+ {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71"},
+ {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5"},
+ {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98"},
+ {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5"},
+ {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0"},
+ {file = "coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb"},
+ {file = "coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505"},
+ {file = "coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2"},
+ {file = "coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056"},
+ {file = "coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc"},
+ {file = "coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9"},
+ {file = "coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf"},
+ {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55"},
+ {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72"},
+ {file = "coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a"},
+ {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6"},
+ {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3"},
+ {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750"},
+ {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39"},
+ {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0"},
+ {file = "coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea"},
+ {file = "coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932"},
+ {file = "coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b"},
+ {file = "coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0"},
+ {file = "coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91"},
+]
+
+[package.extras]
+toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
+
[[package]]
name = "dill"
version = "0.4.1"
@@ -119,6 +239,19 @@ werkzeug = ">=3.1.0"
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+description = "brain-dead simple config-ini parsing"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
+ {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
+]
+
[[package]]
name = "itsdangerous"
version = "2.2.0"
@@ -281,6 +414,130 @@ files = [
[package.dependencies]
dill = ">=0.4.1"
+[[package]]
+name = "packaging"
+version = "26.0"
+description = "Core utilities for Python packages"
+optional = true
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
+ {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = true
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = true
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+ {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pytest"
+version = "9.0.2"
+description = "pytest: simple powerful testing with Python"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"},
+ {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+iniconfig = ">=1.0.1"
+packaging = ">=22"
+pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.3.0"
+description = "Pytest support for asyncio"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"},
+ {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"},
+]
+
+[package.dependencies]
+pytest = ">=8.2,<10"
+typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "pytest-cov"
+version = "7.0.0"
+description = "Pytest plugin for measuring coverage."
+optional = true
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"},
+ {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"},
+]
+
+[package.dependencies]
+coverage = {version = ">=7.10.6", extras = ["toml"]}
+pluggy = ">=1.2"
+pytest = ">=7"
+
+[package.extras]
+testing = ["process-tests", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = true
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "extra == \"dev\" and python_version < \"3.13\""
+files = [
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
+]
+
[[package]]
name = "werkzeug"
version = "3.1.5"
@@ -301,9 +558,9 @@ markupsafe = ">=2.1.1"
watchdog = ["watchdog (>=2.3)"]
[extras]
-dev = ["Flask", "Werkzeug", "docstring-parser", "multiprocess"]
+dev = ["Flask", "Werkzeug", "docstring-parser", "multiprocess", "pytest", "pytest-asyncio", "pytest-cov"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.11"
-content-hash = "627e37896f3041972658be45847057888285034f7602690eb35b5b6d52506353"
+content-hash = "e0f6b68dabc193cb34037f3420dec19fd1b2babea9d700de9471a810499546d8"
diff --git a/pyproject.toml b/pyproject.toml
index 5a53c7f6..6a29fcb3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pedantic"
-version = "2.3.3"
+version = "2.4.0"
description = "Some useful Python decorators for cleaner software development."
readme = "README.md"
requires-python = ">=3.11"
@@ -31,6 +31,9 @@ classifiers = [
# pip install .[dev]
dev = [
"docstring-parser==0.17",
+ "pytest==9.0.2",
+ "pytest-asyncio==1.3.0",
+ "pytest-cov==7.0.0",
"Flask[async]==3.1.2",
"multiprocess==0.70.19",
"Werkzeug==3.1.5",
@@ -38,3 +41,7 @@ dev = [
[tool.setuptools.packages.find]
where = ["."]
+
+[tool.pytest.ini_options]
+addopts = "--doctest-modules"
+python_files = ["*.py"]
diff --git a/pedantic/tests/validate/__init__.py b/tests/__init__.py
similarity index 100%
rename from pedantic/tests/validate/__init__.py
rename to tests/__init__.py
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..f9c8b682
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,12 @@
+import os
+
+import pytest
+
+from pedantic.env_var_logic import ENVIRONMENT_VARIABLE_NAME
+
+
+@pytest.fixture(autouse=True)
+def cleanup_env_vars() -> None:
+ for env_var in [ENVIRONMENT_VARIABLE_NAME, 'foo']:
+ if env_var in os.environ:
+ del os.environ[env_var]
diff --git a/tests/decorators/__init__.py b/tests/decorators/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/decorators/pedantic/__init__.py b/tests/decorators/pedantic/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/decorators/pedantic/test_generator.py b/tests/decorators/pedantic/test_generator.py
new file mode 100644
index 00000000..ed64717d
--- /dev/null
+++ b/tests/decorators/pedantic/test_generator.py
@@ -0,0 +1,113 @@
+from typing import Generator, Iterator, Iterable, List
+
+import pytest
+
+from pedantic.exceptions import PedanticTypeCheckException
+from pedantic.decorators.fn_deco_pedantic import pedantic
+
+
+def test_iterator():
+ @pedantic
+ def gen_func() -> Iterator[int]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ gen = gen_func()
+ next(gen)
+
+
+def test_iterator_wrong_type_hint():
+ @pedantic
+ def genfunc() -> Iterator[float]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ gen = genfunc()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ next(gen)
+
+
+def test_iterator_no_type_args():
+ @pedantic
+ def genfunc() -> Iterator:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ genfunc()
+
+
+def test_iterator_completely_wrong_type_hint():
+ @pedantic
+ def gen_func() -> List[int]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ gen_func()
+
+
+def test_iterable():
+ @pedantic
+ def gen_func() -> Iterable[int]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ gen = gen_func()
+ next(gen)
+
+
+def test_iterable_no_type_args():
+ @pedantic
+ def gen_func() -> Iterable:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ gen_func()
+
+
+def test_generator():
+ @pedantic
+ def gen_func() -> Generator[int, None, str]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+ return 'Done'
+
+ gen = gen_func()
+ next(gen)
+
+
+def test_invalid_no_type_args_generator():
+ @pedantic
+ def gen_func() -> Generator:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+ return 'Done'
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ gen_func()
diff --git a/tests/decorators/pedantic/test_generic_classes.py b/tests/decorators/pedantic/test_generic_classes.py
new file mode 100644
index 00000000..f487d8c3
--- /dev/null
+++ b/tests/decorators/pedantic/test_generic_classes.py
@@ -0,0 +1,315 @@
+from typing import Generic, TypeVar, Any, List, Optional, Union
+
+import pytest
+
+from pedantic.constants import TYPE_VAR_METHOD_NAME, TYPE_VAR_SELF
+from pedantic.decorators.class_decorators import pedantic_class
+from pedantic.exceptions import PedanticTypeVarMismatchException
+
+T = TypeVar('T')
+
+
+def test_pedantic_generic_class():
+ @pedantic_class
+ class LoggedVar(Generic[T]):
+ def __init__(self, value: T, name: str, logger: Any) -> None:
+ self.name = name
+ self.logger = logger
+ self.value = value
+
+ def set(self, new: T) -> None:
+ self.log(message='Set ' + repr(self.value))
+ self.value = new
+
+ def get(self) -> T:
+ self.log(message='Get ' + repr(self.value))
+ return self.value
+
+ def log(self, message: str) -> None:
+ self.logger = self.name + message
+
+ o = LoggedVar[int](value=42, name='hi', logger='test')
+ o.set(new=57)
+ assert isinstance(o.get(), int)
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ o.set(new=3.14)
+
+
+def test_stack():
+ @pedantic_class
+ class Stack(Generic[T]):
+ def __init__(self) -> None:
+ self.items: List[T] = []
+
+ def push(self, item: T) -> None:
+ self.items.append(item)
+
+ def pop(self) -> T:
+ return self.items.pop()
+
+ def empty(self) -> bool:
+ return not self.items
+
+ def top(self) -> Optional[T]:
+ if len(self.items) > 0:
+ return self.items[len(self.items) - 1]
+ else:
+ return None
+
+ my_stack = Stack[str]()
+ get_type_vars = getattr(my_stack, TYPE_VAR_METHOD_NAME)
+ assert get_type_vars() == {T: str, TYPE_VAR_SELF: Stack}
+ with pytest.raises(expected_exception=IndexError):
+ my_stack.pop()
+ assert my_stack.top() is None
+ assert my_stack.top() is None
+ assert T in get_type_vars()
+ my_stack.push(item='hi')
+ assert T in get_type_vars()
+ my_stack.push(item='world')
+ assert T in get_type_vars()
+ assert my_stack.pop() == 'world'
+ assert my_stack.pop() == 'hi'
+ assert my_stack.top() is None
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ my_stack.push(item=42)
+
+ my_other_stack = Stack[int]()
+ get_type_vars = getattr(my_other_stack, TYPE_VAR_METHOD_NAME)
+ assert get_type_vars() == {T: int, TYPE_VAR_SELF: Stack}
+
+ with pytest.raises(expected_exception=IndexError):
+ my_other_stack.pop()
+
+ assert my_other_stack.top() is None
+ assert my_other_stack.top() is None
+ my_other_stack.push(item=100)
+ assert get_type_vars() == {T: int, TYPE_VAR_SELF: Stack}
+ my_other_stack.push(item=142)
+ assert get_type_vars() == {T: int, TYPE_VAR_SELF: Stack}
+ assert my_other_stack.pop() == 142
+ assert my_other_stack.pop() == 100
+ assert my_other_stack.top() is None
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ my_other_stack.push(item='42')
+
+
+def test_generic_class_initialised_without_generics():
+ @pedantic_class
+ class MyClass(Generic[T]):
+ def __init__(self, a: T) -> None:
+ self.a = a
+
+ def get_a(self) -> T:
+ return self.a
+
+ def set_a(self, val: T) -> None:
+ self.a = val
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ _ = MyClass(a=42) # for some reason the exception is only raised when I assign it to a variable
+
+
+def test_generic_class_initialised_without_generics_2():
+ @pedantic_class
+ class MyClass(Generic[T]):
+ def __init__(self, a: T) -> None:
+ self.a = a
+
+ def get_a(self) -> T:
+ return self.a
+
+ def set_a(self, val: T) -> None:
+ self.a = val
+
+ MyClass(a=42) # it is not recognized if it isn't assigned
+
+
+def test_generic_class_inheritance():
+ class Parent:
+ pass
+
+ class Child1(Parent):
+ pass
+
+ class Child2(Parent):
+ pass
+
+ @pedantic_class
+ class MyClass(Generic[T]):
+ def __init__(self, a: T) -> None:
+ self.a = a
+
+ def get_a(self) -> T:
+ return self.a
+
+ def set_a(self, val: T) -> None:
+ self.a = val
+
+ m = MyClass[Parent](a=Child1())
+ assert isinstance(m.get_a(), Child1)
+ assert not isinstance(m.get_a(), Child2)
+ m.set_a(val=Child2())
+ assert isinstance(m.get_a(), Child2)
+ assert not isinstance(m.get_a(), Child1)
+
+
+def test_merge_dicts():
+ def create():
+ @pedantic_class
+ class MyClass(Generic[T]):
+ def __init__(self, a: T) -> None:
+ self.a = a
+
+ def get_a(self) -> T:
+ return self.a
+
+ def set_a(self, val: T) -> None:
+ self.a = val
+ return MyClass(a=42)
+ a = create()
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ a.set_a(val='hi')
+
+
+def test_recursion_depth_exceeded():
+ @pedantic_class
+ class Stack(Generic[T]):
+ def __init__(self) -> None:
+ self.items: List[T] = []
+
+ def len(self) -> int:
+ return len(self.items)
+
+ def push(self, item: T) -> None:
+ self.items.append(item)
+
+ def pop(self) -> T:
+ if len(self.items) > 0:
+ return self.items.pop()
+ else:
+ raise ValueError()
+
+ def empty(self) -> bool:
+ return not self.items
+
+ def top(self) -> Optional[T]:
+ if len(self.items) > 0:
+ return self.items[len(self.items) - 1]
+ else:
+ return None
+
+ def __len__(self) -> int:
+ return len(self.items)
+
+ def create_stack():
+ stack = Stack[int]()
+ return stack
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ stack: Stack[int] = Stack()
+ stack.empty()
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ stack = Stack()
+ stack.empty()
+
+ stack = create_stack()
+ assert stack.empty()
+
+
+def test_generic_union():
+ @pedantic_class
+ class Stack(Generic[T]):
+ def __init__(self) -> None:
+ self.items: List[T] = []
+
+ def len(self) -> int:
+ return len(self.items)
+
+ def push(self, item: T) -> None:
+ self.items.append(item)
+
+ def pop(self) -> T:
+ if len(self.items) > 0:
+ return self.items.pop()
+ else:
+ raise ValueError()
+
+ def empty(self) -> bool:
+ return not self.items
+
+ def top(self) -> Optional[T]:
+ if len(self.items) > 0:
+ return self.items[len(self.items) - 1]
+ else:
+ return None
+
+ def __len__(self) -> int:
+ return len(self.items)
+
+ s = Stack[Union[int, float, str]]()
+ s.push(item=42)
+ s.push(item='hello')
+ s.push(item=3.1415)
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ s.push(item=[1, 2])
+
+
+def test_inheritance():
+ @pedantic_class
+ class Stack(Generic[T]):
+ def __init__(self) -> None:
+ self.items: List[T] = []
+
+ def len(self) -> int:
+ return len(self.items)
+
+ def push(self, item: T) -> None:
+ self.items.append(item)
+
+ def pop(self) -> T:
+ if len(self.items) > 0:
+ return self.items.pop()
+ else:
+ raise ValueError()
+
+ def empty(self) -> bool:
+ return not self.items
+
+ def top(self) -> Optional[T]:
+ if len(self.items) > 0:
+ return self.items[len(self.items) - 1]
+ else:
+ return None
+
+ def __len__(self) -> int:
+ return len(self.items)
+
+ @pedantic_class
+ class Parent:
+ pass
+
+ @pedantic_class
+ class Child1(Parent):
+ pass
+
+ @pedantic_class
+ class Child2(Parent):
+ pass
+
+ parent_stack = Stack[Parent]()
+ parent_stack.push(item=Child1())
+ parent_stack.push(item=Child2())
+ parent_stack.push(item=Parent())
+
+ child_1_stack = Stack[Child1]()
+ child_1_stack.push(item=Child1())
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ child_1_stack.push(item=Child2())
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ child_1_stack.push(item=Parent())
diff --git a/tests/decorators/pedantic/test_pedantic.py b/tests/decorators/pedantic/test_pedantic.py
new file mode 100644
index 00000000..273cfde7
--- /dev/null
+++ b/tests/decorators/pedantic/test_pedantic.py
@@ -0,0 +1,2819 @@
+import os.path
+import types
+import typing
+from dataclasses import dataclass
+from datetime import datetime, date
+from functools import wraps, partial
+from io import BytesIO, StringIO
+from typing import List, Tuple, Callable, Any, Optional, Union, Dict, Set, FrozenSet, NewType, TypeVar, Sequence, \
+ AbstractSet, Iterator, NamedTuple, Collection, Type, Generator, Generic, BinaryIO, TextIO, Iterable, Container, \
+ NoReturn, ClassVar, Literal
+from enum import Enum, IntEnum
+
+import pytest
+
+from pedantic import pedantic_class
+from pedantic.exceptions import PedanticTypeCheckException, PedanticException, PedanticCallWithArgsException, \
+ PedanticTypeVarMismatchException
+from pedantic.decorators.fn_deco_pedantic import pedantic
+
+TEST_FILE = 'test.txt'
+
+
+class Parent:
+ pass
+
+
+class Child(Parent):
+ def method(self, a: int):
+ pass
+
+
+def test_no_kwargs():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ calc(42, 40, 38)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ calc(42, m=40, i=38)
+ calc(n=42, m=40, i=38)
+
+
+def test_nested_type_hints_1():
+ @pedantic
+ def calc(n: int) -> List[List[float]]:
+ return [0.0 * n]
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)
+
+
+def test_nested_type_hints_1_corrected():
+ @pedantic
+ def calc(n: int) -> List[List[float]]:
+ return [[0.0 * n]]
+
+ calc(n=42)
+
+
+def test_nested_type_hints_2():
+ """Problem here: int != float"""
+ @pedantic
+ def calc(n: int) -> List[Tuple[float, str]]:
+ return [(n, str(n))]
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)
+
+
+def test_nested_type_hints_2_corrected():
+ @pedantic
+ def calc(n: int) -> List[Tuple[int, str]]:
+ return [(n, str(n))]
+
+ @pedantic
+ def calc_2(n: float) -> List[Tuple[float, str]]:
+ return [(n, str(n))]
+
+ calc(n=42)
+ calc_2(n=42.0)
+
+
+def test_nested_type_hints_3():
+ """Problem here: inner function actually returns Tuple[int, str]"""
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, str]:
+ return n * x, str(y)
+ return f
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)(x=3, y=3.14)
+
+
+def test_nested_type_hints_3_corrected():
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[int, str]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[int, str]:
+ return n * x, str(y)
+
+ return f
+
+ calc(n=42)(x=3, y=3.14)
+
+
+def test_nested_type_hints_4():
+ """Problem here: return type is actually float"""
+ @pedantic
+ def calc(n: List[List[float]]) -> int:
+ return n[0][0]
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=[[42.0]])
+
+
+def test_nested_type_hints_corrected():
+ @pedantic
+ def calc(n: List[List[float]]) -> int:
+ return int(n[0][0])
+
+ calc(n=[[42.0]])
+
+
+def test_nested_type_hints_5():
+ """Problem here: Tuple[float, str] != Tuple[float, float]"""
+
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, float]:
+ return n * float(x), y
+ return f
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)
+
+
+def test_nested_type_hints_5_corrected():
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[float, float]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, float]:
+ return n * float(x), y
+ return f
+
+ calc(n=42)
+
+
+def test_missing_type_hint_1():
+ """Problem here: type hint for n missed"""
+ @pedantic
+ def calc(n) -> float:
+ return 42.0 * n
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)
+
+
+def test_missing_type_hint_1_corrected():
+ @pedantic
+ def calc(n: int) -> float:
+ return 42.0 * n
+
+ calc(n=42)
+
+
+def test_missing_type_hint_2():
+ """Problem here: Return type annotation missed"""
+ @pedantic
+ def calc(n: int):
+ return 'Hi' + str(n)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42)
+
+
+def test_missing_type_hint_2_corrected():
+ @pedantic
+ def calc(n: int) -> str:
+ return 'Hi' + str(n)
+
+ calc(n=42)
+
+
+def test_missing_type_hint_3():
+ """Problem here: type hint for i missed"""
+ @pedantic
+ def calc(n: int, m: int, i) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=38)
+
+
+def test_missing_type_hint_3_corrected():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ calc(n=42, m=40, i=38)
+
+
+def test_all_ok_2():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> str:
+ return str(n + m + i)
+
+ calc(n=42, m=40, i=38)
+
+
+def test_all_ok_3():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> None:
+ str(n + m + i)
+
+ calc(n=42, m=40, i=38)
+
+
+def test_all_ok_4():
+ @pedantic
+ def calc(n: int) -> List[List[int]]:
+ return [[n]]
+
+ calc(n=42)
+
+
+def test_all_ok_5():
+ @pedantic
+ def calc(n: int) -> List[Tuple[float, str]]:
+ return [(float(n), str(n))]
+
+ calc(n=42)
+
+
+def test_all_ok_6():
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, str]:
+ return n * float(x), str(y)
+ return f
+
+ calc(n=42)(x=72, y=3.14)
+
+
+def test_all_ok_7():
+ @pedantic
+ def calc(n: List[List[float]]) -> Any:
+ return n[0][0]
+
+ calc(n=[[42.0]])
+
+
+def test_all_ok_8():
+ @pedantic
+ def calc(n: int) -> Callable[[int, float], Tuple[float, str]]:
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, str]:
+ return n * float(x), str(y)
+
+ return f
+
+ calc(n=42)(x=3, y=3.14)
+
+
+def test_wrong_type_hint_1():
+ """Problem here: str != int"""
+ @pedantic
+ def calc(n: int, m: int, i: int) -> str:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_1_corrected():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> str:
+ return str(n + m + i)
+
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_2():
+ """Problem here: str != int"""
+ @pedantic
+ def calc(n: int, m: int, i: str) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_2_corrected():
+ @pedantic
+ def calc(n: int, m: int, i: str) -> int:
+ return n + m + int(i)
+
+ calc(n=42, m=40, i='38')
+
+
+def test_wrong_type_hint_3():
+ """Problem here: None != int"""
+ @pedantic
+ def calc(n: int, m: int, i: int) -> None:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_corrected():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> None:
+ print(n + m + i)
+
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_4():
+ """Problem here: None != int"""
+ @pedantic
+ def calc(n: int, m: int, i: int) -> int:
+ print(n + m + i)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=38)
+
+
+def test_wrong_type_hint_4_corrected():
+ @pedantic
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ calc(n=42, m=40, i=38)
+
+
+def test_none_1():
+ """Problem here: None is not accepted"""
+ @pedantic
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=None)
+
+
+def test_none_2():
+ @pedantic
+ def calc(n: int, m: int, i: Optional[int]) -> int:
+ return n + m + i if i is not None else n + m
+
+ calc(n=42, m=40, i=None)
+
+
+def test_none_3():
+ @pedantic
+ def calc(n: int, m: int, i: Union[int, None]) -> int:
+ return n + m + i if i is not None else n + m
+
+ calc(n=42, m=40, i=None)
+
+
+def test_none_4():
+ """Problem here: function may return None"""
+ @pedantic
+ def calc(n: int, m: int, i: Union[int, None]) -> int:
+ return n + m + i if i is not None else None
+
+ calc(n=42, m=40, i=42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(n=42, m=40, i=None)
+
+
+def test_none_5():
+ @pedantic
+ def calc(n: int, m: int, i: Union[int, None]) -> Optional[int]:
+ return n + m + i if i is not None else None
+
+ calc(n=42, m=40, i=None)
+
+
+def test_inheritance_1():
+ class MyClassA:
+ pass
+
+ class MyClassB(MyClassA):
+ pass
+
+ @pedantic
+ def calc(a: MyClassA) -> str:
+ return str(a)
+
+ calc(a=MyClassA())
+ calc(a=MyClassB())
+
+
+def test_inheritance_2():
+ """Problem here: A is not a subtype of B"""
+ class MyClassA:
+ pass
+
+ class MyClassB(MyClassA):
+ pass
+
+ @pedantic
+ def calc(a: MyClassB) -> str:
+ return str(a)
+
+ calc(a=MyClassB())
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(a=MyClassA())
+
+
+def test_instance_method_1():
+ class MyClassA:
+ @pedantic
+ def calc(self, i: int) -> str:
+ return str(i)
+
+ a = MyClassA()
+ a.calc(i=42)
+
+
+def test_instance_method_2():
+ """Problem here: 'i' has no type annotation"""
+ class MyClassA:
+ @pedantic
+ def calc(self, i) -> str:
+ return str(i)
+
+ a = MyClassA()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ a.calc(i=42)
+
+
+def test_instance_method_2_corrected():
+ class MyClassA:
+ @pedantic
+ def calc(self, i: int) -> str:
+ return str(i)
+
+ a = MyClassA()
+ a.calc(i=42)
+
+
+def test_instance_method_int_is_not_float():
+ class MyClassA:
+ @pedantic
+ def calc(self, i: float) -> str:
+ return str(i)
+
+ a = MyClassA()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ a.calc(i=42)
+
+
+def test_instance_method_3_corrected():
+ class MyClassA:
+ @pedantic
+ def calc(self, i: float) -> str:
+ return str(i)
+
+ a = MyClassA()
+ a.calc(i=42.0)
+
+
+def test_instance_method_no_kwargs():
+ class MyClassA:
+ @pedantic
+ def calc(self, i: int) -> str:
+ return str(i)
+
+ a = MyClassA()
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ a.calc(42)
+
+
+def test_instance_method_5():
+ """Problem here: instance methods is not called with kwargs"""
+ class MyClassA:
+ @pedantic
+ def calc(self, i: int) -> str:
+ return str(i)
+
+ a = MyClassA()
+ a.calc(i=42)
+
+
+def test_lambda_1():
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ return lambda x: str(x * i)
+
+ calc(i=42.0)(10.0)
+
+
+def test_lambda_3():
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ def res(x: float) -> str:
+ return str(x * i)
+ return res
+
+ calc(i=42.0)(10.0)
+
+
+def test_lambda_int_is_not_float():
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ def res(x: int) -> str:
+ return str(x * i)
+ return res
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=42.0)(x=10)
+
+
+def test_lambda_4_almost_corrected():
+ """Problem here: float != str"""
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ @pedantic
+ def res(x: int) -> str:
+ return str(x * i)
+ return res
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=42.0)(x=10)
+
+
+def test_lambda_4_almost_corrected_2():
+ @pedantic
+ def calc(i: float) -> Callable[[int], str]:
+ @pedantic
+ def res(x: int) -> str:
+ return str(x * i)
+ return res
+
+ calc(i=42.0)(x=10)
+
+
+def test_lambda_5():
+ """Problem here: float != int"""
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ @pedantic
+ def res(x: float) -> str:
+ return str(x * i)
+ return res
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=42.0)(x=10)
+
+
+def test_lambda_corrected():
+ @pedantic
+ def calc(i: float) -> Callable[[float], str]:
+ @pedantic
+ def res(x: float) -> str:
+ return str(x * i)
+
+ return res
+
+ calc(i=42.0)(x=10.0)
+
+
+def test_tuple_without_type_args():
+ @pedantic
+ def calc(i: Tuple) -> str:
+ return str(i)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=(42.0, 43, 'hi'))
+
+
+def test_tuple_without_args_corrected():
+ @pedantic
+ def calc(i: Tuple[Any, ...]) -> str:
+ return str(i)
+
+ calc(i=(42.0, 43, 'hi'))
+
+
+def test_callable_without_type_args():
+ @pedantic
+ def calc(i: Callable) -> str:
+ return str(i(' you'))
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=lambda x: (42.0, 43, 'hi', x))
+
+
+def test_callable_without_args_correct_with_lambdas():
+ @pedantic
+ def calc(i: Callable[[Any], Tuple[Any, ...]]) -> str:
+ return str(i(x=' you'))
+
+ calc(i=lambda x: (42.0, 43, 'hi', x))
+
+
+def test_callable_without_args_corrected():
+ @pedantic
+ def calc(i: Callable[[Any], Tuple[Any, ...]]) -> str:
+ return str(i(x=' you'))
+
+ @pedantic
+ def arg(x: Any) -> Tuple[Any, ...]:
+ return 42.0, 43, 'hi', x
+ calc(i=arg)
+
+
+def test_list_without_args():
+ @pedantic
+ def calc(i: List) -> Any:
+ return [i]
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(i=[42.0, 43, 'hi'])
+
+
+def test_list_without_args_corrected():
+ @pedantic
+ def calc(i: List[Any]) -> List[List[Any]]:
+ return [i]
+
+ calc(i=[42.0, 43, 'hi'])
+
+
+def test_ellipsis_in_callable_1():
+ @pedantic
+ def calc(i: Callable[..., int]) -> int:
+ return i()
+
+ @pedantic
+ def call() -> int:
+ return 42
+
+ calc(i=call)
+
+
+def test_ellipsis_in_callable_2():
+ @pedantic
+ def calc(i: Callable[..., int]) -> int:
+ return i(x=3.14, y=5)
+
+ @pedantic
+ def call(x: float, y: int) -> int:
+ return 42
+
+ calc(i=call)
+
+
+def test_ellipsis_in_callable_3():
+ """Problem here: call to "call" misses one argument"""
+ @pedantic
+ def calc(i: Callable[..., int]) -> int:
+ return i(x=3.14)
+
+ @pedantic
+ def call(x: float, y: int) -> int:
+ return 42
+
+ with pytest.raises(expected_exception=PedanticException):
+ calc(i=call)
+
+
+def test_optional_args_1():
+ @pedantic
+ def calc(a: int, b: int = 42) -> int:
+ return a + b
+
+ calc(a=2)
+
+
+def test_optional_args_2():
+ @pedantic
+ def calc(a: int = 3, b: int = 42, c: float = 5.0) -> float:
+ return a + b + c
+
+ calc()
+ calc(a=1)
+ calc(b=1)
+ calc(c=1.0)
+ calc(a=1, b=1)
+ calc(a=1, c=1.0)
+ calc(b=1, c=1.0)
+ calc(a=1, b=1, c=1.0)
+
+
+def test_optional_args_3():
+ """Problem here: optional argument c: 5 is not a float"""
+ @pedantic
+ def calc(a: int = 3, b: int = 42, c: float = 5) -> float:
+ return a + b + c
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc()
+
+
+def test_optional_args_3_corrected():
+ @pedantic
+ def calc(a: int = 3, b: int = 42, c: float = 5.0) -> float:
+ return a + b + c
+
+ calc()
+
+
+def test_optional_args_4():
+ class MyClass:
+ @pedantic
+ def foo(self, a: int, b: Optional[int] = 1) -> int:
+ return a + b
+
+ my_class = MyClass()
+ my_class.foo(a=10)
+
+
+def test_optional_args_5():
+ @pedantic
+ def calc(d: Optional[Dict[int, int]] = None) -> Optional[int]:
+ if d is None:
+ return None
+ return sum(d.keys())
+
+ calc(d=None)
+ calc()
+ calc(d={42: 3})
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(d={42: 3.14})
+
+
+def test_optional_args_6():
+ """"Problem here: str != int"""
+ @pedantic
+ def calc(d: int = 42) -> int:
+ return int(d)
+
+ calc(d=99999)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(d='999999')
+
+
+def test_enum_1():
+ """Problem here: Type hint for 'a' should be MyEnum instead of MyEnum.GAMMA"""
+ class MyEnum(Enum):
+ ALPHA = 'startEvent'
+ BETA = 'task'
+ GAMMA = 'sequenceFlow'
+
+ class MyClass:
+ @pedantic
+ def operation(self, a: MyEnum.GAMMA) -> None:
+ print(a)
+
+ m = MyClass()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.operation(a=MyEnum.GAMMA)
+
+
+def test_enum_1_corrected():
+ class MyEnum(Enum):
+ ALPHA = 'startEvent'
+ BETA = 'task'
+ GAMMA = 'sequenceFlow'
+
+ @pedantic
+ def operation(a: MyEnum) -> None:
+ print(a)
+
+ operation(a=MyEnum.GAMMA)
+
+
+def test_sloppy_types_dict():
+ @pedantic
+ def operation(d: dict) -> int:
+ return len(d.keys())
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d={1: 1, 2: 2})
+
+
+def test_sloppy_types_dict_almost_corrected_no_type_args():
+ @pedantic
+ def operation(d: Dict) -> int:
+ return len(d.keys())
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d={1: 1, 2: 2})
+
+
+def test_sloppy_types_dict_corrected():
+ @pedantic
+ def operation(d: Dict[int, int]) -> int:
+ return len(d.keys())
+
+ operation(d={1: 1, 2: 2})
+
+
+def test_sloppy_types_list():
+ @pedantic
+ def operation(d: list) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=[1, 2, 3, 4])
+
+
+def test_sloppy_types_list_almost_corrected_no_type_args():
+ @pedantic
+ def operation(d: List) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=[1, 2, 3, 4])
+
+
+def test_sloppy_types_list_corrected():
+ @pedantic
+ def operation(d: List[int]) -> int:
+ return len(d)
+
+ operation(d=[1, 2, 3, 4])
+
+
+def test_sloppy_types_tuple():
+ @pedantic
+ def operation(d: tuple) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=(1, 2, 3))
+
+
+def test_sloppy_types_tuple_almost_corrected_no_type_args():
+ @pedantic
+ def operation(d: Tuple) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=(1, 2, 3))
+
+
+def test_sloppy_types_tuple_corrected():
+ @pedantic
+ def operation(d: Tuple[int, int, int]) -> int:
+ return len(d)
+
+ operation(d=(1, 2, 3))
+
+
+def test_sloppy_types_set():
+ @pedantic
+ def operation(d: set) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d={1, 2, 3})
+
+
+def test_sloppy_types_set_almost_corrected_to_type_args():
+ @pedantic
+ def operation(d: Set) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d={1, 2, 3})
+
+
+def test_sloppy_types_set_corrected():
+ @pedantic
+ def operation(d: Set[int]) -> int:
+ return len(d)
+
+ operation(d={1, 2, 3})
+
+
+def test_sloppy_types_frozenset():
+ @pedantic
+ def operation(d: frozenset) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=frozenset({1, 2, 3}))
+
+
+def test_sloppy_types_frozenset_almost_corrected_no_type_args():
+ @pedantic
+ def operation(d: FrozenSet) -> int:
+ return len(d)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ operation(d=frozenset({1, 2, 3}))
+
+
+def test_sloppy_types_frozenset_corrected():
+ @pedantic
+ def operation(d: FrozenSet[int]) -> int:
+ return len(d)
+
+ operation(d=frozenset({1, 2, 3}))
+
+
+def test_type_list_but_got_tuple():
+ @pedantic
+ def calc(ls: List[Any]) -> int:
+ return len(ls)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ calc(ls=(1, 2, 3))
+
+
+def test_type_list_corrected():
+ @pedantic
+ def calc(ls: Tuple[Any, ...]) -> int:
+ return len(ls)
+
+ calc(ls=(1, 2, 3))
+
+
+def test_any():
+ @pedantic
+ def calc(ls: List[Any]) -> Dict[int, Any]:
+ return {i: ls[i] for i in range(0, len(ls))}
+
+ calc(ls=[1, 2, 3])
+ calc(ls=[1.11, 2.0, 3.0])
+ calc(ls=['1', '2', '3'])
+ calc(ls=[10.5, '2', (3, 4, 5)])
+
+
+def test_aliases():
+ Vector = List[float]
+
+ @pedantic
+ def scale(scalar: float, vector: Vector) -> Vector:
+ return [scalar * num for num in vector]
+
+ scale(scalar=2.0, vector=[1.0, -4.2, 5.4])
+
+
+def test_new_type():
+ UserId = NewType('UserId', int)
+
+ @pedantic
+ def get_user_name(user_id: UserId) -> str:
+ return str(user_id)
+
+ some_id = UserId(524313)
+ get_user_name(user_id=some_id)
+
+ # the following would be desirable but impossible to check at runtime:
+ # with pytest.raises(expected_exception=AssertionError):
+ # get_user_name(user_id=-1)
+
+
+def test_list_of_new_type():
+ UserId = NewType('UserId', int)
+
+ @pedantic
+ def get_user_name(user_ids: List[UserId]) -> str:
+ return str(user_ids)
+
+ get_user_name(user_ids=[UserId(524313), UserId(42)])
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ get_user_name(user_ids=[UserId(524313), UserId(42), 430.0])
+
+
+def test_callable_no_args():
+ @pedantic
+ def f(g: Callable[[], str]) -> str:
+ return g()
+
+ @pedantic
+ def greetings() -> str:
+ return 'hello world'
+
+ f(g=greetings)
+
+
+def test_type_var():
+ T = TypeVar('T')
+
+ @pedantic
+ def first(ls: List[T]) -> T:
+ return ls[0]
+
+ first(ls=[1, 2, 3])
+
+
+def test_type_var_wrong():
+ T = TypeVar('T')
+
+ @pedantic
+ def first(ls: List[T]) -> T:
+ return str(ls[0])
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ first(ls=[1, 2, 3])
+
+
+def test_type_var_wrong_sequence():
+ T = TypeVar('T')
+
+ @pedantic
+ def first(ls: Sequence[T]) -> T:
+ return str(ls[0])
+
+ with pytest.raises(expected_exception=PedanticTypeVarMismatchException):
+ first(ls=[1, 2, 3])
+
+
+def test_double_pedantic():
+ @pedantic
+ @pedantic
+ def f(x: int, y: float) -> Tuple[float, str]:
+ return float(x), str(y)
+
+ f(x=5, y=3.14)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f(x=5.0, y=3.14)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ f(5, 3.14)
+
+
+def test_args_kwargs():
+ @pedantic
+ def some_method(a: int = 0, b: float = 0.0) -> float:
+ return a * b
+
+ @pedantic
+ def wrapper_method(*args: Union[int, float], **kwargs: Union[int, float]) -> float:
+ return some_method(*args, **kwargs)
+
+ some_method()
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ some_method(3, 3.0)
+ some_method(a=3, b=3.0)
+ wrapper_method()
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ wrapper_method(3, 3.0)
+ wrapper_method(a=3, b=3.0)
+
+
+def test_args_kwargs_no_type_hint():
+ @pedantic
+ def method_no_type_hint(*args, **kwargs) -> None:
+ print(args)
+ print(kwargs)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ method_no_type_hint(a=3, b=3.0)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ method_no_type_hint()
+
+
+def test_args_kwargs_wrong_type_hint():
+ """See: https://www.python.org/dev/peps/pep-0484/#arbitrary-argument-lists-and-default-argument-values"""
+ @pedantic
+ def wrapper_method(*args: str, **kwargs: str) -> None:
+ print(args)
+ print(kwargs)
+
+ wrapper_method()
+ wrapper_method('hi', 'you', ':)')
+ wrapper_method(a='hi', b='you', c=':)')
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ wrapper_method('hi', 'you', ':)', 7)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ wrapper_method(3, 3.0)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ wrapper_method(a=3, b=3.0)
+
+
+def test_additional_kwargs():
+ @pedantic
+ def some_method(a: int, b: float = 0.0, **kwargs: int) -> float:
+ return sum([a, b])
+
+ some_method(a=5)
+ some_method(a=5, b=0.1)
+ some_method(a=5, b=0.1, c=4)
+ some_method(a=5, b=0.1, c=4, d=5, e=6)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_method(a=5, b=0.1, c=4, d=5.0, e=6)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_method(a=5.0, b=0.1, c=4, d=5, e=6)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_method(a=5, b=0, c=4, d=5, e=6)
+
+
+def test_args_kwargs_different_types():
+ @pedantic
+ def foo(*args: str, **kwds: int) -> None:
+ print(args)
+ print(kwds)
+
+ foo('a', 'b', 'c')
+ foo(x=1, y=2)
+ foo('', z=0)
+
+
+def test_pedantic_on_class():
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ @pedantic
+ class MyClass:
+ pass
+ MyClass()
+
+
+def test_is_subtype_tuple():
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ @pedantic
+ def foo() -> Callable[[Tuple[float, str]], Tuple[int]]:
+ def bar(a: Tuple[float]) -> Tuple[int]:
+ return len(a[1]) + int(a[0]),
+ return bar
+ foo()
+
+
+def test_is_subtype_tuple_corrected():
+ @pedantic
+ def foo() -> Callable[[Tuple[float, str]], Tuple[int]]:
+ def bar(a: Tuple[float, str]) -> Tuple[int]:
+ return len(a[1]) + int(a[0]),
+ return bar
+ foo()
+
+
+def test_forward_ref():
+ class Conversation:
+ pass
+
+ @pedantic
+ def get_conversations() -> List['Conversation']:
+ return [Conversation(), Conversation()]
+
+ get_conversations()
+
+
+def test_alternative_list_type_hint():
+ @pedantic
+ def _is_digit_in_int(digit: [int], num: int) -> bool:
+ num_str = str(num)
+ for i in num_str:
+ if int(i) == digit:
+ return True
+ return False
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ _is_digit_in_int(digit=4, num=42)
+
+
+def test_callable_with_union_return():
+ class MyClass:
+ pass
+
+ @pedantic
+ def admin_required(func: Callable[..., Union[str, MyClass]]) -> Callable[..., Union[str, MyClass]]:
+ @wraps(func)
+ def decorated_function(*args, **kwargs):
+ return func(*args, **kwargs)
+ return decorated_function
+
+ @admin_required
+ @pedantic
+ def get_server_info() -> str:
+ return 'info'
+
+ get_server_info()
+
+
+def test_pedantic():
+ @pedantic
+ def foo(a: int, b: str) -> str:
+ return 'abc'
+
+ assert foo(a=4, b='abc') == 'abc'
+
+
+def test_pedantic_always():
+ @pedantic
+ def foo(a: int, b: str) -> str:
+ return 'abc'
+
+ assert foo(a=4, b='abc') == 'abc'
+
+
+def test_pedantic_arguments_fail():
+ @pedantic
+ def foo(a: int, b: str) -> str:
+ return 'abc'
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ foo(a=4, b=5)
+
+
+def test_pedantic_return_type_fail():
+ @pedantic
+ def foo(a: int, b: str) -> str:
+ return 6
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ foo(a=4, b='abc')
+
+
+def test_return_type_none():
+ @pedantic
+ def foo() -> None:
+ return 'a'
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ foo()
+
+
+def test_marco():
+ @pedantic_class
+ class A:
+ def __init__(self, val: int) -> None:
+ self.val = val
+
+ def __eq__(self, other: 'A') -> bool: # other: A and all subclasses
+ return self.val == other.val
+
+ @pedantic_class
+ class B(A):
+ def __init__(self, val: int) -> None:
+ super().__init__(val=val)
+
+ @pedantic_class
+ class C(A):
+ def __init__(self, val: int) -> None:
+ super().__init__(val=val)
+
+ a = A(val=42)
+ b = B(val=42)
+ c = C(val=42)
+
+ assert a == b # works
+ assert a == c # works
+ assert b == c # error
+
+
+def test_date_datetime():
+ @pedantic
+ def foo(a: datetime, b: date) -> None:
+ pass
+
+ foo(a=datetime(1995, 2, 5), b=date(1987, 8, 7))
+ foo(a=datetime(1995, 2, 5), b=datetime(1987, 8, 7))
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ foo(a=date(1995, 2, 5), b=date(1987, 8, 7))
+
+
+def test_any_type():
+ @pedantic
+ def foo(a: Any) -> None:
+ pass
+
+ foo(a='aa')
+
+
+def test_callable_exact_arg_count():
+ @pedantic
+ def foo(a: Callable[[int, str], int]) -> None:
+ pass
+
+ def some_callable(x: int, y: str) -> int:
+ pass
+
+ foo(a=some_callable)
+
+
+def test_callable_bad_type():
+ @pedantic
+ def foo(a: Callable[..., int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_callable_too_few_arguments():
+ @pedantic
+ def foo(a: Callable[[int, str], int]) -> None:
+ pass
+
+ def some_callable(x: int) -> int:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=some_callable)
+
+
+def test_callable_mandatory_kwonlyargs():
+ @pedantic
+ def foo(a: Callable[[int, str], int]) -> None:
+ pass
+
+ def some_callable(x: int, y: str, *, z: float, bar: str) -> int:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=some_callable)
+
+
+def test_callable_class():
+ """
+ Test that passing a class as a callable does not count the "self" argument "a"gainst the
+ ones declared in the Callable specification.
+
+ """
+ @pedantic
+ def foo(a: Callable[[int, str], Any]) -> None:
+ pass
+
+ class SomeClass:
+ def __init__(self, x: int, y: str):
+ pass
+
+ foo(a=SomeClass)
+
+
+def test_callable_plain():
+ @pedantic
+ def foo(a: Callable[..., Any]) -> None:
+ pass
+
+ def callback(a):
+ pass
+
+ foo(a=callback)
+
+
+def test_callable_bound_method():
+ @pedantic
+ def foo(callback: Callable[[int], Any]) -> None:
+ pass
+
+ foo(callback=Child().method)
+
+
+def test_callable_defaults():
+ """
+ Test that a callable having "too many" arguments don't raise an error if the extra
+ arguments have default values.
+
+ """
+ @pedantic
+ def foo(callback: Callable[[int, str], Any]) -> None:
+ pass
+
+ def some_callable(x: int, y: str, z: float = 1.2) -> int:
+ pass
+
+ foo(callback=some_callable)
+
+
+def test_callable_builtin():
+ @pedantic
+ def foo(callback: types.BuiltinFunctionType) -> None:
+ pass
+
+ foo(callback=[].append)
+
+
+def test_dict_bad_type():
+ @pedantic
+ def foo(a: Dict[str, int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_dict_bad_key_type():
+ @pedantic
+ def foo(a: Dict[str, int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a={1: 2})
+
+
+def test_dict_bad_value_type():
+ @pedantic
+ def foo(a: Dict[str, int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a={'x': 'a'})
+
+
+def test_list_bad_type():
+ @pedantic
+ def foo(a: List[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_list_bad_element():
+ @pedantic
+ def foo(a: List[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=[1, 2, 'bb'])
+
+
+def test_sequence_bad_type():
+ @pedantic
+ def foo(a: Sequence[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_sequence_bad_element():
+ @pedantic
+ def foo(a: Sequence[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=[1, 2, 'bb'])
+
+
+def test_abstractset_custom_type():
+ T = TypeVar('T')
+
+ @pedantic_class
+ class DummySet(AbstractSet[T]):
+ def __contains__(self, x: object) -> bool:
+ return x == 1
+
+ def __len__(self) -> T:
+ return 1
+
+ def __iter__(self) -> Iterator[T]:
+ yield 1
+
+ @pedantic
+ def foo(a: AbstractSet[int]) -> None:
+ pass
+
+ foo(a=DummySet[int]())
+
+
+def test_abstractset_bad_type():
+ @pedantic
+ def foo(a: AbstractSet[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_set_bad_type():
+ @pedantic
+ def foo(a: Set[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_abstractset_bad_element():
+ @pedantic
+ def foo(a: AbstractSet[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a={1, 2, 'bb'})
+
+
+def test_set_bad_element():
+ @pedantic
+ def foo(a: Set[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a={1, 2, 'bb'})
+
+
+def test_tuple_bad_type():
+ @pedantic
+ def foo(a: Tuple[int]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=5)
+
+
+def test_tuple_too_many_elements():
+ @pedantic
+ def foo(a: Tuple[int, str]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=(1, 'aa', 2))
+
+
+def test_tuple_too_few_elements():
+ @pedantic
+ def foo(a: Tuple[int, str]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=(1,))
+
+
+def test_tuple_bad_element():
+ @pedantic
+ def foo(a: Tuple[int, str]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=(1, 2))
+
+
+def test_tuple_ellipsis_bad_element():
+ @pedantic
+ def foo(a: Tuple[int, ...]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=(1, 2, 'blah'))
+
+
+def test_namedtuple():
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+
+ @pedantic
+ def foo(bar: Employee) -> None:
+ print(bar)
+
+ foo(bar=Employee('bob', 1))
+
+
+def test_namedtuple_key_mismatch():
+ Employee1 = NamedTuple('Employee', [('name', str), ('id', int)])
+ Employee2 = NamedTuple('Employee', [('firstname', str), ('id', int)])
+
+ @pedantic
+ def foo(bar: Employee1) -> None:
+ print(bar)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(bar=Employee2('bob', 1))
+
+
+def test_namedtuple_type_mismatch():
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+
+ @pedantic
+ def foo(bar: Employee) -> None:
+ print(bar)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(bar=('bob', 1))
+
+
+def test_namedtuple_huge_type_mismatch():
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+
+ @pedantic
+ def foo(bar: int) -> None:
+ print(bar)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(bar=foo(bar=Employee('bob', 1)))
+
+
+def test_namedtuple_wrong_field_type():
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+
+ @pedantic
+ def foo(bar: Employee) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(bar=Employee(2, 1))
+
+
+def test_union():
+ @pedantic
+ def foo(a: Union[str, int]) -> None:
+ pass
+
+ for value in [6, 'xa']:
+ foo(a=value)
+
+
+def test_union_new_syntax():
+ @pedantic
+ def foo(a: str | int) -> None:
+ pass
+
+ for value in [6, 'xa']:
+ foo(a=value)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1.7)
+
+
+def test_union_typing_type():
+ @pedantic
+ def foo(a: Union[str, Collection]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1)
+
+
+def test_union_fail():
+ @pedantic
+ def foo(a: Union[str, int]) -> None:
+ pass
+
+ for value in [5.6, b'xa']:
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=value)
+
+
+def test_type_var_constraints():
+ T = TypeVar('T', int, str)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ for values in [
+ {'a': 6, 'b': 7},
+ {'a': 'aa', 'b': "bb"},
+ ]:
+ foo(**values)
+
+
+def test_type_var_constraints_fail_typing_type():
+ T = TypeVar('T', int, Collection)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a='aa', b='bb')
+
+
+def test_typevar_constraints_fail():
+ T = TypeVar('T', int, str)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=2.5, b='aa')
+
+
+def test_typevar_bound():
+ T = TypeVar('T', bound=Parent)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ foo(a=Child(), b=Child())
+
+
+def test_type_var_bound_fail():
+ T = TypeVar('T', bound=Child)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=Parent(), b=Parent())
+
+
+def test_type_var_invariant_fail():
+ T = TypeVar('T', int, str)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=2, b=3.6)
+
+
+def test_type_var_covariant():
+ T = TypeVar('T', covariant=True)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ foo(a=Parent(), b=Child())
+
+
+def test_type_var_covariant_fail():
+ T = TypeVar('T', covariant=True)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeVarMismatchException):
+ foo(a=Child(), b=Parent())
+
+
+def test_type_var_contravariant():
+ T = TypeVar('T', contravariant=True)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ foo(a=Child(), b=Parent())
+
+
+def test_type_var_contravariant_fail():
+ T = TypeVar('T', contravariant=True)
+
+ @pedantic
+ def foo(a: T, b: T) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeVarMismatchException):
+ foo(a=Parent(), b=Child())
+
+
+def test_class_bad_subclass():
+ @pedantic
+ def foo(a: Type[Child]) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=Parent)
+
+
+def test_class_any():
+ @pedantic
+ def foo(a: Type[Any]) -> None:
+ pass
+
+ foo(a=str)
+
+
+def test_wrapped_function():
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+ return wrapper
+
+ @pedantic
+ @decorator
+ def foo(a: 'Child') -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=Parent())
+
+
+def test_mismatching_default_type():
+ @pedantic
+ def foo(a: str = 1) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo()
+
+
+def test_implicit_default_none():
+ """
+ Test that if the default value is ``None``, a ``None`` argument can be passed.
+
+ """
+ @pedantic
+ def foo(a: Optional[str] = None) -> None:
+ pass
+
+ foo()
+
+
+def test_generator_simple():
+ """Test that argument type checking works in a generator function too."""
+ @pedantic
+ def generate(a: int) -> Generator[int, int, None]:
+ yield a
+ yield a + 1
+
+ gen = generate(a=1)
+ next(gen)
+
+
+def test_wrapped_generator_no_return_type_annotation():
+ """Test that return type checking works in a generator function too."""
+ @pedantic
+ def generate(a: int) -> Generator[int, int, None]:
+ yield a
+ yield a + 1
+
+ gen = generate(a=1)
+ next(gen)
+
+
+def test_varargs():
+ @pedantic
+ def foo(*args: int) -> None:
+ pass
+
+ foo(1, 2)
+
+
+def test_varargs_fail():
+ @pedantic
+ def foo(*args: int) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(1, 'a')
+
+
+def test_kwargs():
+ @pedantic
+ def foo(**kwargs: int) -> None:
+ pass
+
+ foo(a=1, b=2)
+
+
+def test_kwargs_fail():
+ @pedantic
+ def foo(**kwargs: int) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1, b='a')
+
+
+def test_generic():
+ T_Foo = TypeVar('T_Foo')
+
+ class FooGeneric(Generic[T_Foo]):
+ pass
+
+ @pedantic
+ def foo(a: FooGeneric[str]) -> None:
+ print(a)
+
+ foo(a=FooGeneric[str]())
+
+
+def test_newtype():
+ myint = NewType("myint", int)
+
+ @pedantic
+ def foo(a: myint) -> int:
+ return 42
+
+ assert foo(a=1) == 42
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a="a")
+
+
+def test_collection():
+ @pedantic
+ def foo(a: Collection) -> None:
+ pass
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=True)
+
+
+def test_binary_io():
+ @pedantic
+ def foo(a: BinaryIO) -> None:
+ print(a)
+
+ foo(a=BytesIO())
+
+
+def test_text_io():
+ @pedantic
+ def foo(a: TextIO) -> None:
+ print(a)
+
+ foo(a=StringIO())
+
+
+def test_binary_io_fail():
+ @pedantic
+ def foo(a: TextIO) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=BytesIO())
+
+
+def test_text_io_fail():
+ @pedantic
+ def foo(a: BinaryIO) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=StringIO())
+
+
+def test_binary_io_real_file():
+ @pedantic
+ def foo(a: BinaryIO) -> None:
+ print(a)
+
+ with open(file=TEST_FILE, mode='wb') as f:
+ foo(a=f)
+
+ os.remove(TEST_FILE)
+
+
+def test_text_io_real_file():
+ @pedantic
+ def foo(a: TextIO) -> None:
+ print(a)
+
+ with open(file=TEST_FILE, mode='w') as f:
+ foo(a=f)
+
+ os.remove(TEST_FILE)
+
+
+def test_pedantic_return_type_var_fail():
+ T = TypeVar('T', int, float)
+
+ @pedantic
+ def foo(a: T, b: T) -> T:
+ return 'a'
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=4, b=2)
+
+
+def test_callable():
+ @pedantic
+ def foo_1(a: Callable[..., int]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Callable) -> None:
+ print(a)
+
+ def some_callable() -> int:
+ return 4
+
+ foo_1(a=some_callable)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a=some_callable)
+
+
+def test_list():
+ @pedantic
+ def foo_1(a: List[int]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: List) -> None:
+ print(a)
+
+ @pedantic
+ def foo_3(a: list) -> None:
+ print(a)
+
+ @pedantic
+ def foo_4(a: list[int]) -> None:
+ print(a)
+
+ foo_1(a=[1, 2])
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a=[1, 2])
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_3(a=[1, 2])
+
+ foo_4(a=[1, 2])
+
+
+def test_dict():
+ @pedantic
+ def foo_1(a: Dict[str, int]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Dict) -> None:
+ print(a)
+
+ @pedantic
+ def foo_3(a: dict) -> None:
+ print(a)
+
+ @pedantic
+ def foo_4(a: dict[str, int]) -> None:
+ print(a)
+
+ foo_1(a={'x': 2})
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a={'x': 2})
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_3(a={'x': 2})
+
+ foo_4(a={'x': 2})
+
+
+def test_sequence():
+ @pedantic
+ def foo(a: Sequence[str]) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ foo(a=value)
+
+
+def test_sequence_no_type_args():
+ @pedantic
+ def foo(a: Sequence) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=value)
+
+
+def test_iterable():
+ @pedantic
+ def foo(a: Iterable[str]) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ foo(a=value)
+
+
+def test_iterable_no_type_args():
+ @pedantic
+ def foo(a: Iterable) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=value)
+
+
+def test_container():
+ @pedantic
+ def foo(a: Container[str]) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ foo(a=value)
+
+
+def test_container_no_type_args():
+ @pedantic
+ def foo(a: Container) -> None:
+ print(a)
+
+ for value in [('a', 'b'), ['a', 'b'], 'abc']:
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=value)
+
+
+def test_set():
+ @pedantic
+ def foo_1(a: AbstractSet[int]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Set[int]) -> None:
+ print(a)
+
+ for value in [set(), {6}]:
+ foo_1(a=value)
+ foo_2(a=value)
+
+
+def test_set_no_type_args():
+ @pedantic
+ def foo_1(a: AbstractSet) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Set) -> None:
+ print(a)
+
+ @pedantic
+ def foo_3(a: set) -> None:
+ print(a)
+
+ for value in [set(), {6}]:
+ with pytest.raises(PedanticTypeCheckException):
+ foo_1(a=value)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a=value)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_3(a=value)
+
+
+def test_tuple():
+ @pedantic
+ def foo_1(a: Tuple[int, int]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Tuple[int, ...]) -> None:
+ print(a)
+
+ foo_1(a=(1, 2))
+ foo_2(a=(1, 2))
+
+
+def test_tuple_no_type_args():
+ @pedantic
+ def foo_1(a: Tuple) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: tuple) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_1(a=(1, 2))
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a=(1, 2))
+
+
+def test_empty_tuple():
+ @pedantic
+ def foo(a: Tuple[()]) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=())
+
+
+def test_class():
+ @pedantic
+ def foo_1(a: Type[Parent]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: Type[TypeVar('UnboundType')]) -> None:
+ print(a)
+
+ @pedantic
+ def foo_3(a: Type[TypeVar('BoundType', bound=Parent)]) -> None:
+ print(a)
+
+ foo_1(a=Child)
+ foo_2(a=Child)
+ foo_3(a=Child)
+
+
+def test_class_no_type_vars():
+ @pedantic
+ def foo_1(a: Type) -> None:
+ print(a)
+
+ @pedantic
+ def foo_2(a: type) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_1(a=Child)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo_2(a=Child)
+
+
+def test_class_not_a_class():
+ @pedantic
+ def foo(a: Type[Parent]) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1)
+
+
+def test_complex():
+ @pedantic
+ def foo(a: complex) -> None:
+ print(a)
+
+ foo(a=complex(1, 5))
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1.0)
+
+
+def test_float():
+ @pedantic
+ def foo(a: float) -> None:
+ print(a)
+
+ foo(a=1.5)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=1)
+
+
+def test_coroutine_correct_return_type():
+ @pedantic
+ async def foo() -> str:
+ return 'foo'
+
+ coro = foo()
+
+ with pytest.raises(StopIteration):
+ coro.send(None)
+
+
+def test_coroutine_wrong_return_type():
+ @pedantic
+ async def foo() -> str:
+ return 1
+
+ coro = foo()
+
+ with pytest.raises(PedanticTypeCheckException):
+ coro.send(None)
+
+
+def test_bytearray_bytes():
+ @pedantic
+ def foo(x: bytearray) -> None:
+ pass
+
+ foo(x=bytearray([1]))
+
+
+def test_class_decorator():
+ @pedantic_class
+ class Foo:
+ @staticmethod
+ def staticmethod() -> int:
+ return 'foo'
+
+ @classmethod
+ def classmethod(cls) -> int:
+ return 'foo'
+
+ def method(self) -> int:
+ return 'foo'
+
+ with pytest.raises(PedanticTypeCheckException):
+ Foo.staticmethod()
+
+ with pytest.raises(PedanticTypeCheckException):
+ Foo.classmethod()
+
+ with pytest.raises(PedanticTypeCheckException):
+ Foo().method()
+
+
+def test_generator():
+ @pedantic
+ def genfunc() -> Generator[int, str, List[str]]:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ gen = genfunc()
+
+ with pytest.raises(StopIteration):
+ value = next(gen)
+ while True:
+ value = gen.send(str(value))
+ assert isinstance(value, int)
+
+
+def test_generator_no_type_args():
+ @pedantic
+ def genfunc() -> Generator:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ with pytest.raises(PedanticTypeCheckException):
+ genfunc()
+
+
+def test_iterator():
+ @pedantic
+ def genfunc() -> Iterator[int]:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ gen = genfunc()
+
+ with pytest.raises(PedanticTypeCheckException):
+ value = next(gen)
+ while True:
+ value = gen.send(str(value))
+ assert isinstance(value, int)
+
+
+def test_iterator_no_type_args():
+ @pedantic
+ def genfunc() -> Iterator:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ with pytest.raises(PedanticTypeCheckException):
+ genfunc()
+
+
+def test_iterable_advanced():
+ @pedantic
+ def genfunc() -> Iterable[int]:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ gen = genfunc()
+
+ with pytest.raises(PedanticTypeCheckException):
+ value = next(gen)
+ while True:
+ value = gen.send(str(value))
+ assert isinstance(value, int)
+
+
+def test_iterable_advanced_no_type_args():
+ @pedantic
+ def genfunc() -> Iterable:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ with pytest.raises(PedanticTypeCheckException):
+ genfunc()
+
+
+def test_generator_bad_yield():
+ @pedantic
+ def genfunc_1() -> Generator[int, str, None]:
+ yield 'foo'
+
+ @pedantic
+ def genfunc_2() -> Iterable[int]:
+ yield 'foo'
+
+ @pedantic
+ def genfunc_3() -> Iterator[int]:
+ yield 'foo'
+
+ gen = genfunc_1()
+
+ with pytest.raises(PedanticTypeCheckException):
+ next(gen)
+
+ gen = genfunc_2()
+
+ with pytest.raises(PedanticTypeCheckException):
+ next(gen)
+
+ gen = genfunc_3()
+
+ with pytest.raises(PedanticTypeCheckException):
+ next(gen)
+
+
+def test_generator_bad_send():
+ @pedantic
+ def genfunc() -> Generator[int, str, None]:
+ yield 1
+ yield 2
+
+ gen = genfunc()
+ next(gen)
+
+ with pytest.raises(PedanticTypeCheckException):
+ gen.send(2)
+
+
+def test_generator_bad_return():
+ @pedantic
+ def genfunc() -> Generator[int, str, str]:
+ yield 1
+ return 6
+
+ gen = genfunc()
+ next(gen)
+
+ with pytest.raises(PedanticTypeCheckException):
+ gen.send('foo')
+
+
+def test_return_generator():
+ @pedantic
+ def genfunc() -> Generator[int, None, None]:
+ yield 1
+
+ @pedantic
+ def foo() -> Generator[int, None, None]:
+ return genfunc()
+
+ foo()
+
+
+def test_local_class():
+ @pedantic_class
+ class LocalClass:
+ class Inner:
+ pass
+
+ def create_inner(self) -> 'Inner':
+ return self.Inner()
+
+ retval = LocalClass().create_inner()
+ assert isinstance(retval, LocalClass.Inner)
+
+
+def test_local_class_async():
+ @pedantic_class
+ class LocalClass:
+ class Inner:
+ pass
+
+ async def create_inner(self) -> 'Inner':
+ return self.Inner()
+
+ coro = LocalClass().create_inner()
+
+ with pytest.raises(StopIteration):
+ coro.send(None)
+
+
+def test_callable_nonmember():
+ class CallableClass:
+ def __call__(self):
+ pass
+
+ @pedantic_class
+ class LocalClass:
+ some_callable = CallableClass()
+
+
+def test_inherited_class_method():
+ @pedantic_class
+ class Parent:
+ @classmethod
+ def foo(cls, x: str) -> str:
+ return cls.__name__
+
+ @pedantic_class
+ class Child(Parent):
+ pass
+
+ assert Child.foo(x='bar') == 'Parent'
+
+ with pytest.raises(PedanticTypeCheckException):
+ Child.foo(x=1)
+
+
+def test_type_var_forward_ref_bound():
+ TBound = TypeVar('TBound', bound='Parent')
+
+ @pedantic
+ def func(x: TBound) -> None:
+ pass
+
+ func(x=Parent())
+
+ with pytest.raises(PedanticTypeCheckException):
+ func(x='foo')
+
+
+def test_noreturn():
+ @pedantic
+ def foo() -> NoReturn:
+ pass
+
+ @pedantic
+ def bar() -> NoReturn:
+ raise ZeroDivisionError('bar')
+
+ with pytest.raises(expected_exception=ZeroDivisionError):
+ bar()
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo()
+
+
+def test_literal():
+ @pedantic
+ def foo(a: Literal[1, True, 'x', b'y', 404]) -> None:
+ print(a)
+
+ foo(a=404)
+ foo(a=True)
+ foo(a='x')
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=4)
+
+
+def test_literal_union():
+ @pedantic
+ def foo(a: Union[str, Literal[1, 6, 8]]) -> None:
+ print(a)
+
+ foo(a=6)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=4)
+
+
+def test_literal_illegal_value():
+ @pedantic
+ def foo(a: Literal[1, 1.1]) -> None:
+ print(a)
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo(a=4)
+
+
+def test_enum():
+ with pytest.raises(PedanticTypeCheckException):
+ @pedantic_class
+ class MyEnum(Enum):
+ A = 'a'
+
+
+def test_enum_aggregate():
+ T = TypeVar('T', bound=IntEnum)
+
+ @pedantic_class
+ class EnumAggregate(Generic[T]):
+ enum: ClassVar[Type[T]]
+
+ def __init__(self, value: Union[int, str, List[T]]) -> None:
+ assert len(self.enum) < 10
+
+ if value == '':
+ raise ValueError(f'Parameter "value" cannot be empty!')
+
+ if isinstance(value, list):
+ self._value = ''.join([str(x.value) for x in value])
+ else:
+ self._value = str(value)
+
+ self._value = ''.join(sorted(self._value)) # sort characters in string
+ self.to_list() # check if is valid
+
+ def __contains__(self, item: T) -> bool:
+ return item in self.to_list()
+
+ def __eq__(self, other: Union['EnumAggregate', str]) -> bool:
+ if isinstance(other, str):
+ return self._value == other
+
+ return self._value == other._value
+
+ def __str__(self) -> str:
+ return self._value
+
+ def to_list(self) -> List[T]:
+ return [self.enum(int(character)) for character in self._value]
+
+ @property
+ def value(self) -> str:
+ return self._value
+
+ @classmethod
+ def all(cls) -> str:
+ return ''.join([str(x.value) for x in cls.enum])
+
+ class Gender(IntEnum):
+ MALE = 1
+ FEMALE = 2
+ DIVERS = 3
+
+ @pedantic_class
+ class Genders(EnumAggregate[Gender]):
+ enum = Gender
+
+ Genders(value=12)
+
+ with pytest.raises(PedanticTypeCheckException):
+ Genders(value=Child())
+
+
+def test_primitive_list_dict_tuple():
+ @pedantic
+ def f(x: list[dict[int, tuple[float, str]]]) -> list[Any]:
+ return x
+
+ f(x=[{3: (3.24, 'hi')}])
+
+ for value in [
+ [{3, (3, 'hi')}],
+ [{3: (3, 'hi')}],
+ [{3: (3.24, 3)}],
+ [{3: (3.24, 'hi')}, {0}],
+ ]:
+ with pytest.raises(PedanticTypeCheckException):
+ f(x=value)
+
+
+def test_dataclass_protocol():
+ class IsDataclass(typing.Protocol):
+ __dataclass_fields__: ClassVar[Dict]
+
+ @dataclass
+ class Foo:
+ v: int
+
+ @pedantic
+ def foo(x: IsDataclass) -> IsDataclass:
+ return x
+
+ foo(x=Foo(v=42))
+
+
+def test_dataclass_protocol_in_type():
+ class IsDataclass(typing.Protocol):
+ __dataclass_fields__: ClassVar[Dict]
+
+ @dataclass
+ class Foo:
+ v: int
+
+ @pedantic
+ def foo(x: type[IsDataclass]) -> IsDataclass:
+ return x
+
+ assert foo(x=Foo) == Foo
+
+
+def test_dataclass_protocol_in_type_with_union():
+ class IsDataclass(typing.Protocol):
+ __dataclass_fields__: ClassVar[Dict]
+
+ @dataclass
+ class Foo:
+ v: int
+
+ @pedantic
+ def foo(x: type[None | bool | IsDataclass]) -> IsDataclass:
+ return x
+
+ assert foo(x=Foo) == Foo
+
+
+def test_partial_function():
+ @pedantic
+ def f(a: int, b: int) -> int:
+ return a + b
+
+ g = pedantic(partial(f, a=1))
+
+ assert f(a=2, b=3) == 5
+ assert g(b=3) == 4
+
+ with pytest.raises(PedanticTypeCheckException):
+ f(a='2', b=3)
+
+ with pytest.raises(PedanticTypeCheckException):
+ g(b='2')
+
+
+def test_typing_never():
+ from typing import Never
+
+ @pedantic
+ def never_call_me(arg: Never) -> None:
+ pass
+
+ @pedantic
+ def foo() -> Never:
+ pass
+
+ @pedantic
+ def bar() -> Never:
+ raise ZeroDivisionError('bar')
+
+ with pytest.raises(expected_exception=ZeroDivisionError):
+ bar()
+
+ with pytest.raises(PedanticTypeCheckException):
+ foo()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException) as exc:
+ never_call_me(arg='42')
+
+
+def test_literal_string():
+ from typing import LiteralString
+
+ @pedantic
+ def foo(s: LiteralString) -> None:
+ pass
+
+ foo(s='Hi')
+ foo(s=2 * 'Hi')
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ foo(s=3)
+
+
+def test_self_type():
+ from typing import Self
+
+ class Bar:
+ pass
+
+ @pedantic_class
+ class Foo:
+ def f(self) -> Self:
+ return self
+
+ @staticmethod
+ def g() -> Self:
+ return Foo()
+
+ @classmethod
+ def h(cls) -> Self:
+ return cls()
+
+ def f_2(self) -> Self:
+ return Bar()
+
+ @staticmethod
+ def g_2() -> Self:
+ return Bar()
+
+ @classmethod
+ def h_2(cls) -> Self:
+ return Bar()
+
+ f = Foo()
+ assert f.f() == f
+ f.g()
+ f.h()
+ Foo.g()
+ Foo.h()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f.f_2()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f.g_2()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f.h_2()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo.g_2()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo.h_2()
+
+
+def test_using_self_type_annotation_outside_class():
+ from typing import Self
+
+ @pedantic
+ def f() -> Self:
+ return 'hi'
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f()
+
+
+def test_type_var_tuple():
+ from typing import TypeVarTuple, Generic
+
+ Ts = TypeVarTuple('Ts')
+
+ @pedantic_class
+ class Array(Generic[*Ts]):
+ def __init__(self, *args: *Ts) -> None:
+ self._values = args
+
+ @pedantic
+ def add_dimension(a: Array[*Ts], value: int) -> Array[int, *Ts]:
+ return Array[int, *Ts](value, *a._values)
+
+ array = Array[int, float](42, 3.4)
+ array_2 = Array[bool, int, float, str](True, 4, 3.4, 'hi')
+ extended_array = add_dimension(a=array, value=42)
+ assert extended_array._values == (42, 42, 3.4)
+
+ # this is too complicated at the moment
+ # with pytest.raises(expected_exception=PedanticTypeCheckException):
+ # Array[int, float](4.2, 3.4)
diff --git a/tests/decorators/pedantic/test_pedantic_async.py b/tests/decorators/pedantic/test_pedantic_async.py
new file mode 100644
index 00000000..aa9b9221
--- /dev/null
+++ b/tests/decorators/pedantic/test_pedantic_async.py
@@ -0,0 +1,65 @@
+import asyncio
+
+import pytest
+
+from pedantic.decorators.class_decorators import pedantic_class
+from pedantic.exceptions import PedanticTypeCheckException
+from pedantic.decorators.fn_deco_pedantic import pedantic
+
+
+@pytest.mark.asyncio
+async def test_coroutine_correct_return_type():
+ @pedantic
+ async def foo() -> str:
+ await asyncio.sleep(0)
+ return 'foo'
+
+ await foo()
+
+
+@pytest.mark.asyncio
+async def test_coroutine_wrong_return_type():
+ @pedantic
+ async def foo() -> str:
+ await asyncio.sleep(0)
+ return 1
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ await foo()
+
+
+@pytest.mark.asyncio
+async def test_coroutine_wrong_argument_type():
+ @pedantic
+ async def foo(x: int) -> int:
+ await asyncio.sleep(0)
+ return 1 + x
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ await foo(x=4.5)
+
+
+@pytest.mark.asyncio
+async def test_static_async():
+ @pedantic_class
+ class Foo:
+ @staticmethod
+ async def staticmethod() -> int:
+ await asyncio.sleep(0)
+ return 'foo'
+
+ @classmethod
+ async def classmethod(cls) -> int:
+ await asyncio.sleep(0)
+ return 'foo'
+
+ async def method(self) -> int:
+ await asyncio.sleep(0)
+ return 'foo'
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ await Foo.staticmethod()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ await Foo.classmethod()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ await Foo().method()
diff --git a/tests/decorators/pedantic/test_pedantic_class.py b/tests/decorators/pedantic/test_pedantic_class.py
new file mode 100644
index 00000000..c63bf9c4
--- /dev/null
+++ b/tests/decorators/pedantic/test_pedantic_class.py
@@ -0,0 +1,565 @@
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+from enum import IntEnum
+from typing import Any, Optional, Callable, Union, Dict, List
+
+import pytest
+
+from pedantic.env_var_logic import disable_pedantic
+from pedantic import overrides
+from pedantic.decorators.class_decorators import pedantic_class
+from pedantic.exceptions import PedanticOverrideException, PedanticTypeCheckException, \
+ PedanticCallWithArgsException
+
+
+def test_constructor():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ MyClass(a=42)
+
+
+def test_constructor_with_list():
+ class Foo(IntEnum):
+ A = 1
+ B = 2
+
+ @pedantic_class
+ class MyClass:
+ def __init__(self, b: int, a: List[Foo]) -> None:
+ self.a = a
+ self.b = b
+
+ MyClass(b=42, a=[Foo.A, Foo.B])
+
+
+def test_constructor_param_without_type_hint():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a) -> None:
+ self.a = a
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass(a=42)
+
+
+def test_constructor_without_return_type():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int):
+ self.a = a
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass(a=42)
+
+
+def test_constructor_wrong_return_type():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int) -> int:
+ self.a = a
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass(a=42)
+
+
+def test_constructor_must_be_called_with_kwargs():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ MyClass(42)
+
+
+def test_multiple_methods():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ def calc(self, b: int) -> int:
+ return self.a - b
+
+ def print(self, s: str) -> None:
+ print(f'{self.a} and {s}')
+
+ m = MyClass(a=5)
+ m.calc(b=42)
+ m.print(s='Hi')
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.calc(45)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.calc(b=45.0)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.print('Hi')
+
+
+def test_multiple_methods_with_missing_and_wrong_type_hints():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ def calc(self, b: int) -> float:
+ return self.a - b
+
+ def dream(self, b) -> int:
+ return self.a * b
+
+ def print(self, s: str):
+ print(f'{self.a} and {s}')
+
+ m = MyClass(a=5)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.calc(b=42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.print(s='Hi')
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.dream(b=2)
+
+
+def test_type_annotation_string():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ self.s = s
+
+ @staticmethod
+ def generator() -> 'MyClass':
+ return MyClass(s='generated')
+
+ MyClass.generator()
+
+
+def test_typo_in_type_annotation_string():
+ @pedantic_class
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ self.s = s
+
+ @staticmethod
+ def generator() -> 'MyClas':
+ return MyClass(s='generated')
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.generator()
+
+
+def test_overriding_contains():
+ @pedantic_class
+ class MyClass(list):
+ def __contains__(self, item: int) -> bool:
+ return True
+
+ m = MyClass()
+ print(42 in m)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ print('something' in m)
+
+
+def test_type_annotation_string_typo():
+ @pedantic_class
+ class MyClass:
+ def compare(self, other: 'MyClas') -> bool:
+ return self == other
+
+ def fixed_compare(self, other: 'MyClass') -> bool:
+ return self == other
+
+ m = MyClass()
+ m.fixed_compare(other=m)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.compare(other=m)
+
+
+def test_docstring_not_required():
+ @pedantic_class
+ class Foo:
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ def bunk(self) -> int:
+ '''
+ Function with correct docstring. Yes, single-quoted docstrings are allowed too.
+ Returns:
+ int: 42
+ '''
+ return self.a
+
+ foo = Foo(a=10)
+ foo.bunk()
+
+
+def test_overrides():
+ @pedantic_class
+ class Abstract:
+ def func(self, b: str) -> str:
+ pass
+
+ def bunk(self) -> int:
+ pass
+
+ @pedantic_class
+ class Foo(Abstract):
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ @overrides(Abstract)
+ def func(self, b: str) -> str:
+ return b
+
+ @overrides(Abstract)
+ def bunk(self) -> int:
+ return 42
+
+ f = Foo(a=42)
+ f.func(b='Hi')
+ f.bunk()
+
+
+def test_overrides_abc():
+ @pedantic_class
+ class Abstract(ABC):
+ @abstractmethod
+ def func(self, b: str) -> str:
+ pass
+
+ @abstractmethod
+ def bunk(self) -> int:
+ pass
+
+ @pedantic_class
+ class Foo(Abstract):
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ @overrides(Abstract)
+ def func(self, b: str) -> str:
+ return b
+
+ @overrides(Abstract)
+ def bunk(self) -> int:
+ return 42
+
+ f = Foo(a=42)
+ f.func(b='Hi')
+ f.bunk()
+
+
+def test_overrides_with_type_errors_and_call_by_args3():
+ @pedantic_class
+ class Abstract:
+ def func(self, b: str) -> str:
+ pass
+
+ def bunk(self) -> int:
+ pass
+
+ @pedantic_class
+ class Foo(Abstract):
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ @overrides(Abstract)
+ def func(self, b: str) -> str:
+ return b
+
+ @overrides(Abstract)
+ def bunk(self) -> int:
+ return self.a
+
+ f = Foo(a=42)
+ f.func(b='Hi')
+ f.bunk()
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ f.func('Hi')
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo(a=3.1415)
+ f.a = 3.145
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ f.bunk()
+
+
+def test_overrides_goes_wrong():
+ @pedantic_class
+ class Parent:
+ def func(self, b: str) -> str:
+ return b + b + b
+
+ def bunk(self) -> int:
+ return 42
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ @pedantic_class
+ class Foo(Parent):
+ def __init__(self, a: int) -> None:
+ self.a = a
+
+ @overrides(Parent)
+ def funcy(self, b: str) -> str:
+ return b
+
+ @overrides(Parent)
+ def bunk(self) -> int:
+ return self.a
+
+ f = Foo(a=40002)
+ f.func(b='Hi')
+ f.bunk()
+
+ p = Parent()
+ p.func(b='Hi')
+ p.bunk()
+
+
+def test_static_method_with_sloppy_type_annotation():
+ @pedantic_class
+ class MyStaticClass:
+ @staticmethod
+ def double_func(a: int) -> int:
+ x, y = MyStaticClass.static_bar()
+ return x
+
+ @staticmethod
+ def static_bar() -> (int, int): # this is wrong. Correct would be Tuple[int, int]
+ return 0, 1
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ print(MyStaticClass.double_func(a=0))
+
+
+def test_property():
+ @pedantic_class
+ class MyClass(object):
+ def __init__(self, some_arg: Any) -> None:
+ self._some_attribute = some_arg
+
+ @property
+ def some_attribute(self) -> int:
+ return self._some_attribute
+
+ @some_attribute.setter
+ def some_attribute(self, value: str) -> None:
+ self._some_attribute = value
+
+ def calc(self, value: float) -> float:
+ return 2 * value
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ MyClass(42)
+
+ m = MyClass(some_arg=42)
+ assert m.some_attribute == 42
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_attribute = 100
+
+ assert m.some_attribute == 42
+ m.some_attribute = '100'
+ assert m._some_attribute == '100'
+ m.calc(value=42.0)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ print(m.some_attribute)
+
+
+def test_property_getter_and_setter_misses_type_hints():
+ @pedantic_class
+ class MyClass(object):
+ def __init__(self, some_arg: int) -> None:
+ self._some_attribute = some_arg
+
+ @property
+ def some_attribute(self):
+ return self._some_attribute
+
+ @some_attribute.setter
+ def some_attribute(self, value: int):
+ self._some_attribute = value
+
+ def calc(self, value: float) -> float:
+ return 2 * value
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ MyClass(42)
+
+ m = MyClass(some_arg=42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_attribute = 100
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ print(m.some_attribute)
+ m.calc(value=42.0)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.calc(value=42)
+
+
+def test_default_constructor():
+ @pedantic_class
+ class MyClass:
+ def fun(self) -> int:
+ return 42
+ m = MyClass()
+ m.fun()
+
+
+def test_optional_callable():
+ @pedantic_class
+ class SemanticSimilarity:
+ def __init__(self, post_processing: bool = True, val: Optional[Callable[[float], float]] = None) -> None:
+ if post_processing is None:
+ self.post_processing = val
+ else:
+ self.post_processing = lambda x: x
+
+ SemanticSimilarity()
+
+
+def test_optional_lambda():
+ @pedantic_class
+ class SemanticSimilarity:
+ def __init__(self, val: Callable[[float], float] = lambda x: x) -> None:
+ self.post_processing = val
+
+ SemanticSimilarity()
+
+
+def test_class_method_type_annotation_missing():
+ @pedantic_class
+ class MyClass:
+ @classmethod
+ def do(cls):
+ print('i did something')
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.do()
+
+
+def test_class_method_type_annotation():
+ @pedantic_class
+ class MyClass:
+ @classmethod
+ def do(cls) -> None:
+ print('i did something')
+
+ @classmethod
+ def calc(cls, x: Union[int, float]) -> int:
+ return x * x
+
+ MyClass.do()
+ MyClass.calc(x=5)
+ m = MyClass()
+ m.do()
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.calc(5)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.calc(x=5.1)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.calc('hi')
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.calc(5)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.calc(x=5.1)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.calc('hi')
+
+
+def test_dataclass_inside():
+ """Pedantic cannot be used on dataclasses."""
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ @pedantic_class
+ @dataclass
+ class MyClass:
+ name: str
+ unit_price: float
+ quantity_on_hand: int = 0
+
+
+def test_dataclass_outside():
+ """Pedantic cannot check the constructor of dataclasses"""
+
+ @dataclass
+ @pedantic_class
+ class MyClass:
+ name: str
+ unit_price: float
+ quantity_on_hand: int = 0
+
+ def total_cost(self) -> int:
+ return self.unit_price * self.quantity_on_hand
+
+ MyClass(name='name', unit_price=5.1)
+ a = MyClass(name='name', unit_price=5.0, quantity_on_hand=42)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ a.total_cost()
+
+
+def test_class_decorator_static_class_method():
+ @pedantic_class
+ class Foo:
+ @staticmethod
+ def staticmethod() -> int:
+ return 'foo'
+
+ @classmethod
+ def classmethod(cls) -> int:
+ return 'foo'
+
+ def method(self) -> int:
+ return 'foo'
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo.staticmethod()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo.classmethod()
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ Foo().method()
+
+
+def test_pedantic_class_disable_pedantic():
+ disable_pedantic()
+
+ @pedantic_class
+ class MyClass:
+ def __init__(self, pw, **kwargs):
+ self._validate_str_len(new_values=kwargs)
+
+ @staticmethod
+ def _validate_str_len(new_values: Dict[str, Any]) -> None:
+ return 42
+
+ def method(pw, **kwargs):
+ MyClass._validate_str_len(new_values=kwargs)
+
+ MyClass._validate_str_len(None)
+ MyClass._validate_str_len(new_values={1: 1, 2: 2})
+ MyClass(name='hi', age=12, pw='123')
+
+
+def test_disable_pedantic_2():
+ """ https://github.com/LostInDarkMath/pedantic-python-decorators/issues/37 """
+
+ disable_pedantic()
+
+ @pedantic_class
+ class Foo:
+ def __init__(self) -> None:
+ self._value = 42
+
+ def do(self) -> None:
+ print(self.bar(value=self._value))
+
+ @staticmethod
+ def bar(value: int) -> int:
+ return value + 75
+
+ f = Foo()
+ f.do()
diff --git a/tests/decorators/pedantic/test_pedantic_class_docstring.py b/tests/decorators/pedantic/test_pedantic_class_docstring.py
new file mode 100644
index 00000000..7d81a7da
--- /dev/null
+++ b/tests/decorators/pedantic/test_pedantic_class_docstring.py
@@ -0,0 +1,87 @@
+import pytest
+
+from pedantic import pedantic_class_require_docstring
+from pedantic.exceptions import PedanticDocstringException
+
+
+def test_require_docstring():
+ @pedantic_class_require_docstring
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ """Constructor
+
+ Args:
+ s (str): name
+ """
+ self.s = s
+
+ def double(self, b: int) -> str:
+ """some method
+
+ Args:
+ b (int): magic number
+
+ Returns:
+ str: cool stuff
+
+ """
+ return self.s + str(b)
+
+ @staticmethod
+ def generator() -> 'MyClass':
+ """Static
+
+ Returns:
+ MyClass: instance
+ """
+ return MyClass(s='generated')
+
+ m = MyClass.generator()
+ m.double(b=42)
+
+
+def test_typo_docstring():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_class_require_docstring
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ """Constructor
+
+ Args:
+ s (str): name
+ """
+ self.s = s
+
+ @staticmethod
+ def generator() -> 'MyClass':
+ """Static
+
+ Returns:
+ MyClas: instance
+ """
+ return MyClass(s='generated')
+
+
+def test_wrong_docstring():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_class_require_docstring
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ """Constructor
+
+ Args:
+ s (str): name
+ """
+ self.s = s
+
+ def double(self, b: int) -> str:
+ """some method
+
+ Args:
+ b (float): magic number
+
+ Returns:
+ str: cool stuff
+
+ """
+ return self.s + str(b)
diff --git a/tests/decorators/test_async_context_manager.py b/tests/decorators/test_async_context_manager.py
new file mode 100644
index 00000000..bd0f167b
--- /dev/null
+++ b/tests/decorators/test_async_context_manager.py
@@ -0,0 +1,84 @@
+import pytest
+
+from pedantic.decorators import safe_async_contextmanager
+
+
+@pytest.mark.asyncio
+async def test_safe_context_manager_no_exception():
+ before = False
+ after = False
+
+ @safe_async_contextmanager
+ async def foo():
+ nonlocal before, after
+ before = True
+ yield 42
+ after = True
+
+ assert before is False
+ assert after is False
+
+ async with foo() as f:
+ assert before is True
+ assert after is False
+ assert f == 42
+
+ assert before is True
+ assert after is True
+
+
+@pytest.mark.asyncio
+async def test_safe_context_manager_with_exception():
+ before = False
+ after = False
+
+ @safe_async_contextmanager
+ async def foo():
+ nonlocal before, after
+ before = True
+ yield 42
+ after = True
+
+ assert before is False
+ assert after is False
+
+ with pytest.raises(expected_exception=ValueError):
+ async with foo() as f:
+ assert before is True
+ assert after is False
+ assert f == 42
+ raise ValueError('oh no')
+
+ assert before is True
+ assert after is True
+
+
+@pytest.mark.asyncio
+async def test_safe_context_manager_with_args_kwargs():
+ @safe_async_contextmanager
+ async def foo(a, b):
+ yield a, b
+
+ async with foo(42, b=43) as f:
+ assert f == (42, 43)
+
+
+def test_safe_context_manager_async():
+ with pytest.raises(expected_exception=AssertionError) as err:
+ @safe_async_contextmanager
+ def foo(a, b):
+ yield a, b
+
+ expected = 'foo is not an async generator. So you need to use "safe_contextmanager" instead.'
+ assert err.value.args[0] == expected
+
+
+@pytest.mark.asyncio
+async def test_safe_context_manager_non_generator():
+ with pytest.raises(expected_exception=AssertionError) as err:
+ @safe_async_contextmanager
+ async def foo(a, b):
+ return a, b
+
+ expected = 'foo is not a generator.'
+ assert err.value.args[0] == expected
diff --git a/tests/decorators/test_class_decorators.py b/tests/decorators/test_class_decorators.py
new file mode 100644
index 00000000..08162bcd
--- /dev/null
+++ b/tests/decorators/test_class_decorators.py
@@ -0,0 +1,35 @@
+from pedantic.decorators.class_decorators import trace_class, timer_class
+
+
+def test_trace_class():
+ @trace_class
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ self.s = s
+
+ def double(self, b: int) -> str:
+ return self.s + str(b)
+
+ @staticmethod
+ def generator() -> 'MyClass':
+ return MyClass(s='generated')
+
+ m = MyClass.generator()
+ m.double(b=42)
+
+
+def test_timer_class():
+ @timer_class
+ class MyClass:
+ def __init__(self, s: str) -> None:
+ self.s = s
+
+ def double(self, b: int) -> str:
+ return self.s + str(b)
+
+ @staticmethod
+ def generator() -> 'MyClass':
+ return MyClass(s='generated')
+
+ m = MyClass.generator()
+ m.double(b=42)
diff --git a/tests/decorators/test_combination_of_decorators.py b/tests/decorators/test_combination_of_decorators.py
new file mode 100644
index 00000000..3768b81c
--- /dev/null
+++ b/tests/decorators/test_combination_of_decorators.py
@@ -0,0 +1,174 @@
+from abc import ABC, abstractmethod
+
+import pytest
+
+from pedantic.decorators.class_decorators import pedantic_class, for_all_methods
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.validators import Min
+from pedantic.exceptions import PedanticException, PedanticTypeCheckException, PedanticCallWithArgsException
+from pedantic.decorators.fn_deco_pedantic import pedantic
+from pedantic import overrides
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, Parameter, ReturnAs
+
+
+def test_pedantic_overrides():
+ class MyClass(ABC):
+ @pedantic
+ @abstractmethod
+ def op(self, a: int) -> None:
+ pass
+
+ class Child(MyClass):
+ a = 0
+
+ @pedantic
+ @overrides(MyClass)
+ def op(self, a: int) -> None:
+ self.a = a
+
+ c = Child()
+ c.op(a=42)
+
+
+def test_pedantic_below_validate():
+ @validate(
+ Parameter(name='x', validators=[Min(0)]),
+ return_as=ReturnAs.KWARGS_WITH_NONE,
+ )
+ @pedantic
+ def some_calculation(x: int) -> int:
+ return x
+
+ some_calculation(x=42)
+ some_calculation(42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(x=-1)
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(x=-42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_calculation(x=1.0)
+
+
+def test_pedantic_above_validate():
+ @pedantic
+ @validate(
+ Parameter(name='x', validators=[Min(0)]),
+ )
+ def some_calculation(x: int) -> int:
+ return x
+
+ some_calculation(x=42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(x=-1)
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(x=-42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_calculation(x=1.0)
+ with pytest.raises(expected_exception=PedanticException):
+ some_calculation(42)
+
+
+def test_pedantic_above_validate_on_instance_method():
+ class MyClass:
+ @pedantic
+ @validate(
+ Parameter(name='x', validators=[Min(0)]),
+ )
+ def some_calculation(self, x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-1)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_calculation(x=1.0)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.some_calculation(42)
+
+
+def test_pedantic_below_validate_on_instance_method():
+ class MyClass:
+ @validate(
+ Parameter(name='x', validators=[Min(0)]),
+ )
+ @pedantic
+ def some_calculation(self, x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ m.some_calculation(42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-1)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_calculation(x=1.0)
+
+
+def test_pedantic_class_with_validate_instance_method():
+ @pedantic_class
+ class MyClass:
+ @validate(
+ Parameter(name='x', validators=[Min(0)]),
+ )
+ def some_calculation(self, x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-1)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(x=-42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_calculation(x=1.0)
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ m.some_calculation(42)
+
+
+def test_pedantic_class_static_method_1():
+ @pedantic_class
+ class MyClass:
+ @staticmethod
+ def some_calculation(x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ MyClass.some_calculation(x=45)
+
+
+def test_pedantic_class_static_method_2():
+ """Never do this, but it works"""
+ @for_all_methods(staticmethod)
+ @pedantic_class
+ class MyClass:
+ def some_calculation(x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.some_calculation(x=42.0)
+ MyClass.some_calculation(x=45)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ MyClass.some_calculation(x=45.0)
+
+
+def test_pedantic_static_method_1():
+ class MyClass:
+ @staticmethod
+ @pedantic
+ def some_calculation(x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(x=42)
+ MyClass.some_calculation(x=45)
diff --git a/tests/decorators/test_context_manager.py b/tests/decorators/test_context_manager.py
new file mode 100644
index 00000000..c4a0c4b1
--- /dev/null
+++ b/tests/decorators/test_context_manager.py
@@ -0,0 +1,80 @@
+import pytest
+
+from pedantic.decorators import safe_contextmanager
+
+
+def test_safe_context_manager_no_exception():
+ before = False
+ after = False
+
+ @safe_contextmanager
+ def foo():
+ nonlocal before, after
+ before = True
+ yield 42
+ after = True
+
+ assert before is False
+ assert after is False
+
+ with foo() as f:
+ assert before is True
+ assert after is False
+ assert f == 42
+
+ assert before is True
+ assert after is True
+
+
+def test_safe_context_manager_with_exception():
+ before = False
+ after = False
+
+ @safe_contextmanager
+ def foo():
+ nonlocal before, after
+ before = True
+ yield 42
+ after = True
+
+ assert before is False
+ assert after is False
+
+ with pytest.raises(expected_exception=ValueError):
+ with foo() as f:
+ assert before is True
+ assert after is False
+ assert f == 42
+ raise ValueError('oh no')
+
+ assert before is True
+ assert after is True
+
+
+def test_safe_context_manager_with_args_kwargs():
+ @safe_contextmanager
+ def foo(a, b):
+ yield a, b
+
+ with foo(42, b=43) as f:
+ assert f == (42, 43)
+
+
+def test_safe_context_manager_async():
+ with pytest.raises(expected_exception=AssertionError) as err:
+ @safe_contextmanager
+ async def foo(a, b):
+ yield a, b
+
+ expected = 'foo is async. So you need to use "safe_async_contextmanager" instead.'
+ assert err.value.args[0] == expected
+
+
+def test_safe_context_manager_non_generator():
+ with pytest.raises(expected_exception=AssertionError) as err:
+ @safe_contextmanager
+ def foo(a, b):
+ return a, b
+
+ expected = 'foo is not a generator.'
+ assert err.value.args[0] == expected
diff --git a/tests/decorators/test_docstring.py b/tests/decorators/test_docstring.py
new file mode 100644
index 00000000..34fc2a4c
--- /dev/null
+++ b/tests/decorators/test_docstring.py
@@ -0,0 +1,500 @@
+from typing import List, Optional
+
+import pytest
+
+from pedantic import is_enabled
+from pedantic.exceptions import PedanticTypeCheckException, PedanticDocstringException
+from pedantic.decorators.fn_deco_pedantic import pedantic_require_docstring, pedantic
+from pedantic.decorators.class_decorators import pedantic_class_require_docstring, pedantic_class
+
+
+def test_no_docstring():
+ assert is_enabled()
+
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+
+def test_one_line_doc_string_missing_arguments_and_return():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(n: int, m: int, i: int) -> int:
+ """Returns the sum of the three args."""
+ return n + m + i
+
+def test_one_line_doc_string_corrected():
+ @pedantic_require_docstring
+ def calc(n: int, m: int, i: int) -> int:
+ """Returns the sum of the three args.
+
+ Args:
+ n (int): something
+ m (int): something
+ i (int): something
+
+ Returns:
+ int: bar
+ """
+ return n + m + i
+
+ calc(n=42, m=40, i=38)
+
+
+def test_list_vs_typing_list():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ list: a list of strings representing the header columns
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_google_docstring_2():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+
+ return [file_loc, str(print_cols)]
+
+ calc(file_loc='Hi', print_cols=False)
+
+
+def test_google_docstring_3():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+
+ return [file_loc, str(print_cols)]
+
+ calc(file_loc='Hi', print_cols=False)
+
+
+def test_more_parameter_documented_than_the_function_takes():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+ amount (int): THIS ARGUMENT IS NOT TAKEN BY THE FUNCTION
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_google_docstring_corrected():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool, amount: int) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+ amount (int): now it is
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+
+ return [file_loc, str(print_cols), str(amount)]
+
+ calc(file_loc='Hi', print_cols=False, amount=42)
+
+
+def test_no_args_keyword_before_documented_arguments():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> list:
+ """Gets and prints the spreadsheet's header columns
+
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ list: a list of strings representing the header columns
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_google_no_return_keyword():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> list:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ list: a list of strings representing the header columns
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_keep_it_simple():
+ @pedantic_require_docstring
+ def calc() -> None:
+ """Gets and prints the spreadsheet's header columns"""
+ pass
+
+ calc()
+
+
+def test_docstring_misses_argument():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(name: str) -> None:
+ """Gets and prints the spreadsheet's header columns"""
+ print('hi ' + name)
+
+
+def test_keep_it_simple_2_corrected():
+ @pedantic_require_docstring
+ def calc(name: str) -> None:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ name (str): the name
+ """
+ print('hi ' + name)
+
+ calc(name='maria')
+
+
+def test_undocumented_arg():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool, number: int) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+ return [file_loc, str(print_cols), str(number)]
+
+
+def test_undocumented_arg_corrected():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool, number: int) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+ number (int): magic number
+
+ Returns:
+ List[str]: a list of strings representing the header columns
+ """
+
+ return [file_loc, str(print_cols), str(number)]
+
+ calc(file_loc='Hi', print_cols=False, number=42)
+
+
+def test_restructured_text_style_doctsring_cannot_be_parsed_yet():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ :param file_loc: The file location of the spreadsheet
+ :type file_loc: str
+ :param print_cols: A flag used to print the columns to the console
+ :type print_cols: bool
+ :returns: a list of strings representing the header column
+ :rtype: List[str]
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_return_nothing_but_document_return_value():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool):
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ list: a list of strings representing the header columns
+ """
+ print([file_loc, str(print_cols)])
+
+
+def test_return_nothing_but_document_return_value_2():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> None:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ list: a list of strings representing the header columns
+ """
+ print([file_loc, str(print_cols)])
+
+
+def test_return_value_1_corrected():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> None:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+ """
+
+ a = [file_loc, str(print_cols)]
+ calc(file_loc='Hi', print_cols=False)
+
+
+def test_return_value_is_not_documented():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ # the error message here is actually wrong due to the behavior of the docstring-parser package
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console (default is False)
+
+ Returns:
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_return_value_2_corrected():
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+
+ Returns:
+ List[str]: results
+ """
+
+ return [file_loc, str(print_cols)]
+
+ calc(file_loc='Hi', print_cols=False)
+
+
+def test_return_value_is_not_documented_3():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc(file_loc: str, print_cols: bool) -> List[str]:
+ """Gets and prints the spreadsheet's header columns
+
+ Args:
+ file_loc (str): The file location of the spreadsheet
+ print_cols (bool): A flag used to print the columns to the console
+ (default is False)
+ """
+ return [file_loc, str(print_cols)]
+
+
+def test_wrong_format_1():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ class MyText:
+ text = 'hi'
+
+ @pedantic_require_docstring
+ def __contains__(self, substring: str) -> bool:
+ """
+ Checks if contains substring.
+ Overriding __contains__ build in functions allows to use the 'in' operator blah readability
+
+ Example:
+ my_text = MyText('abc')
+ if 'ab' in my_text -> true
+ :param: substring: substring
+ :return: True if substring is stored, False otherwise.
+ """
+ return substring in self.text
+
+
+def test_undocumented_arg_3():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic
+ def calc(a: int, b: float, c: str) -> str:
+ """Returns some cool string
+
+ Args:
+ a (int): something
+ b (float): something
+
+ Returns:
+ str: something
+ """
+ return str(a) + str(b) + c
+
+ calc(a=42, b=3.14, c='hi')
+
+
+def test_pedantic_1_corrected():
+ @pedantic
+ def calc(a: int, b: float, c: str) -> str:
+ """Returns some cool string
+
+ Args:
+ a (int): something
+ b (float): something
+ c (str): something
+
+ Returns:
+ str: something
+ """
+ return str(a) + str(b) + c
+
+ calc(a=42, b=3.14, c='hi')
+
+
+def test_documented_none_as_return_type():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def calc() -> None:
+ """some cool stuff
+
+ Returns:
+ None: the evil void
+ """
+ pass
+
+
+def test_exception_in_docstring_parser():
+ @pedantic_class
+ class Foo:
+ def func(self, b: str) -> str:
+ """
+ Function with docstring syntax error below.
+ Args:
+ b (str):
+ simple string
+ Returns:
+ str: simple string
+ """
+ return b
+
+
+def test_user_class():
+ class BPMNEnum:
+ attr = 'BPMNEnum'
+
+ class BPMNElement:
+ attr = 'BPMNElement'
+
+ @pedantic_class_require_docstring
+ class MyClass:
+ def make_element(self, element_type: BPMNEnum,
+ src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
+ """
+ Searches all element_types in XML-DOM and returns corresponding
+ BPMN-Objects.
+ Args:
+ element_type(BPMNEnum): abc
+ src_tgt_elements (Optional[List[BPMNElement]]): abc
+
+ Returns:
+ List[BPMNElement]: abc
+ """
+ element_type.attr = '42'
+ return src_tgt_elements
+
+ m = MyClass()
+ m.make_element(element_type=BPMNEnum(), src_tgt_elements=[BPMNElement()])
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ m.make_element(element_type=BPMNElement(), src_tgt_elements=[BPMNEnum()])
+
+
+def test_user_class_with_typing():
+ class BPMNEnum:
+ attr = 'BPMNEnum'
+
+ class BPMNElement:
+ attr = 'BPMNElement'
+
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_class_require_docstring
+ class MyClass:
+
+ def make_element(self, element_type: BPMNEnum,
+ src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
+ """
+ Searches all element_types in XML-DOM and returns corresponding
+ BPMN-Objects.
+ Args:
+ element_type(BPMNEnum): abc
+ src_tgt_elements (typing.Optional[List[BPMNElement]]): abc
+
+ Returns:
+ List[BPMNElement]: abc
+ """
+ element_type.attr = '42'
+ return src_tgt_elements
+
+
+def test_pedantic_args():
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic(require_docstring=True)
+ def no_docstrings() -> None:
+ pass
+
+ with pytest.raises(expected_exception=PedanticDocstringException):
+ @pedantic_require_docstring
+ def no_docstrings() -> None:
+ pass
diff --git a/tests/decorators/test_frozen_dataclass.py b/tests/decorators/test_frozen_dataclass.py
new file mode 100644
index 00000000..0d9b892a
--- /dev/null
+++ b/tests/decorators/test_frozen_dataclass.py
@@ -0,0 +1,384 @@
+from abc import ABC
+from dataclasses import dataclass, FrozenInstanceError
+from typing import List, Dict, Set, Tuple, Awaitable, Callable, Generic, TypeVar, Optional
+
+import pytest
+
+from pedantic.decorators.cls_deco_frozen_dataclass import frozen_dataclass, frozen_type_safe_dataclass
+from pedantic.exceptions import PedanticTypeCheckException
+
+
+@frozen_dataclass
+class Foo:
+ a: int
+ b: str
+ c: bool
+
+
+@frozen_type_safe_dataclass
+class B:
+ v: Set[int]
+
+
+@frozen_type_safe_dataclass
+class A:
+ foo: List[int]
+ bar: Dict[str, str]
+ values: Tuple[B, B]
+
+
+def test_equals_and_hash():
+ a = Foo(a=6, b='hi', c=True)
+ b = Foo(a=6, b='hi', c=True)
+ c = Foo(a=7, b='hi', c=True)
+
+ assert a == b
+ assert hash(a) == hash(b)
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+
+def test_copy_with():
+ foo = Foo(a=6, b='hi', c=True)
+
+ copy_1 = foo.copy_with()
+ assert foo == copy_1
+
+ copy_2 = foo.copy_with(a=42)
+ assert foo != copy_2
+ assert copy_2.a == 42
+ assert foo.b == copy_2.b
+ assert foo.c == copy_2.c
+
+ copy_3 = foo.copy_with(b='Hello')
+ assert foo != copy_3
+ assert foo.a == copy_3.a
+ assert copy_3.b == 'Hello'
+ assert copy_3.c == foo.c
+
+ copy_4 = foo.copy_with(c=False)
+ assert foo != copy_4
+ assert foo.a == copy_4.a
+ assert foo.b == copy_4.b
+ assert copy_4.c is False
+
+ copy_5 = foo.copy_with(a=676676, b='new', c=False)
+ assert foo != copy_5
+ assert copy_5.a == 676676
+ assert copy_5.b == 'new'
+ assert copy_5.c is False
+
+def test_validate_types():
+ foo = Foo(a=6, b='hi', c=True)
+ foo.validate_types()
+
+ bar = Foo(a=6.6, b='hi', c=True)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException) as err:
+ bar.validate_types()
+
+ expected = 'In dataclass "Foo" in field "a": Type hint is incorrect: Argument 6.6 of type ' \
+ 'does not match expected type .'
+ assert err.value.args[0] == expected
+
+
+def test_frozen_dataclass_above_dataclass():
+ # This is the same behavior like
+ # >>> @dataclass(frozen=True)
+ # ... @dataclass
+ # ... class C:
+ # ... foo: int
+
+ @frozen_dataclass
+ @dataclass
+ class A:
+ foo: int
+
+ with pytest.raises(expected_exception=TypeError):
+ A()
+
+ with pytest.raises(expected_exception=FrozenInstanceError):
+ A(foo=3)
+
+
+def test_frozen_dataclass_below_dataclass():
+ @dataclass
+ @frozen_dataclass
+ class A:
+ foo: int
+
+ with pytest.raises(expected_exception=TypeError):
+ A()
+
+ a = A(foo=3)
+
+ with pytest.raises(expected_exception=FrozenInstanceError):
+ a.foo = 4
+
+ b = a.copy_with(foo=4)
+ assert b.foo == 4
+
+
+def test_frozen_typesafe_dataclass_with_post_init():
+ b = 3
+
+ @frozen_dataclass(type_safe=True)
+ class A:
+ foo: int
+
+ def __post_init__(self) -> None:
+ nonlocal b
+ b = 33
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException) as err:
+ A(foo=42.7)
+
+ assert err.value.args[0] == ('In dataclass "A" in field "foo": Type hint is incorrect: Argument 42.7 of type'
+ ' does not match expected type .')
+
+ # we check that the __post_init__ method is executed
+ assert b == 33
+
+
+def test_frozen_typesafe_dataclass_without_post_init():
+ @frozen_dataclass(type_safe=True)
+ class A:
+ foo: int
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException) as err:
+ A(foo=42.7)
+
+ assert err.value.args[0] == ('In dataclass "A" in field "foo": Type hint is incorrect: Argument 42.7 of type '
+ ' does not match expected type .')
+
+
+def test_frozen_dataclass_with_empty_braces():
+ @frozen_dataclass()
+ class A:
+ foo: int
+
+ a = A(foo=42)
+ assert a.foo == 42
+
+
+def test_frozen_dataclass_no_braces():
+ @frozen_dataclass
+ class A:
+ foo: int
+
+ a = A(foo=42)
+ assert a.foo == 42
+
+
+def test_frozen_dataclass_order():
+ @frozen_dataclass(order=True)
+ class A:
+ foo: int
+ bar: int
+
+ a = A(foo=42, bar=43)
+ b = A(foo=42, bar=42)
+ c = A(foo=41, bar=44)
+ d = A(foo=44, bar=0)
+ assert b < a
+ assert c < b
+ assert a < d
+
+
+def test_frozen_type_safe_dataclass_copy_with_check():
+ @frozen_type_safe_dataclass
+ class A:
+ foo: int
+ bar: bool
+
+ a = A(foo=42, bar=False)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ a.copy_with(foo=1.1)
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ a.copy_with(bar=11)
+
+ a.copy_with(foo=11, bar=True)
+
+
+def test_copy_with_is_shallow():
+ a = A(foo=[1, 2], bar={'hello': 'world'}, values=(B(v={4, 5}), B(v={6})))
+ shallow = a.copy_with()
+
+ # manipulation
+ shallow.bar['hello'] = 'pedantic'
+ shallow.foo.append(3)
+
+ assert a.foo == [1, 2, 3]
+ assert shallow.foo == [1, 2, 3]
+ assert a.bar['hello'] == 'pedantic'
+ assert shallow.bar['hello'] == 'pedantic'
+
+
+def test_copy_with_and_deep_copy_with():
+ a = A(foo=[1, 2], bar={'hello': 'world'}, values=(B(v={4, 5}), B(v={6})))
+ deep = a.deep_copy_with()
+
+ # manipulation
+ deep.bar['hello'] = 'pedantic'
+ deep.foo.append(3)
+
+ assert a.foo == [1, 2]
+ assert deep.foo == [1, 2, 3]
+ assert a.bar['hello'] == 'world'
+ assert deep.bar['hello'] == 'pedantic'
+
+
+def test_frozen_dataclass_inheritance_override_post_init():
+ i = 1
+
+ @frozen_type_safe_dataclass
+ class A:
+ bar: int
+
+ def __post_init__(self):
+ nonlocal i
+ i += 1
+ print('hello a')
+
+ @frozen_type_safe_dataclass
+ class B(A):
+ foo: int
+
+ def __post_init__(self):
+ nonlocal i
+ i *= 10
+ print('hello b')
+
+ A(bar=3)
+ assert i == 2
+
+ b = B(bar=3, foo=42)
+ assert i == 20 # post init of A was not called
+ assert b.bar == 3
+ assert b.foo == 42
+
+ a = b.copy_with()
+ assert a == b
+ assert i == 200
+
+
+def test_frozen_dataclass_inheritance_not_override_post_init():
+ i = 1
+
+ @frozen_type_safe_dataclass
+ class A:
+ bar: int
+
+ def __post_init__(self):
+ nonlocal i
+ i += 1
+ print('hello a')
+
+ @frozen_type_safe_dataclass
+ class B(A):
+ foo: int
+
+ A(bar=3)
+ assert i == 2
+
+ b = B(bar=3, foo=42)
+ assert i == 3 # post init of A was called
+ assert b.bar == 3
+ assert b.foo == 42
+
+ a = b.copy_with()
+ assert a == b
+ assert i == 4
+
+
+def test_type_safe_frozen_dataclass_with_awaitable():
+ @frozen_type_safe_dataclass
+ class A:
+ f: Callable[..., Awaitable[int]]
+
+ async def _cb() -> int:
+ return 42
+
+ async def _cb_2() -> str:
+ return '42'
+
+ A(f=_cb)
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ A(f=_cb_2)
+
+
+def test_type_safe_frozen_dataclass_with_forward_ref():
+ T = TypeVar('T')
+
+ class State(Generic[T], ABC):
+ pass
+
+ class StateMachine(Generic[T], ABC):
+ pass
+
+ @frozen_type_safe_dataclass
+ class StateChangeResult:
+ new_state: Optional['MachineState']
+
+ class MachineState(State['MachineStateMachine']):
+ pass
+
+ class OfflineMachineState(MachineState):
+ pass
+
+ class OnlineMachineState:
+ pass
+
+ class MachineStateMachine(StateMachine[MachineState]):
+ pass
+
+ s = StateChangeResult(new_state=OfflineMachineState())
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ StateChangeResult(new_state=OnlineMachineState())
+
+ s.validate_types()
+
+
+def test_forward_ref_to_itself():
+ """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/72 """
+
+ @frozen_type_safe_dataclass
+ class Comment:
+ replies: List['Comment']
+
+ comment = Comment(replies=[Comment(replies=[])])
+ comment.copy_with(replies=[Comment(replies=[])])
+ comment.validate_types()
+
+
+def test_forward_ref_to_itself_while_class_not_in_scope():
+ """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/72 """
+
+ def _scope():
+ @frozen_type_safe_dataclass
+ class Comment:
+ replies: List['Comment']
+
+ def _make(replies=None):
+ return Comment(replies=replies or [])
+
+ return _make
+
+ make = _scope()
+
+ comment = make(replies=[make(replies=[])])
+ comment.copy_with(replies=[make(replies=[])])
+ comment.validate_types()
+
+
+def test_slots_work_with_equals():
+ @frozen_dataclass(slots=True)
+ class Foo:
+ a: int
+
+ o = Foo(a=0)
+ assert o == o.copy_with()
diff --git a/tests/decorators/test_in_subprocess.py b/tests/decorators/test_in_subprocess.py
new file mode 100644
index 00000000..a8e9a8cd
--- /dev/null
+++ b/tests/decorators/test_in_subprocess.py
@@ -0,0 +1,172 @@
+import asyncio
+import time
+from typing import NoReturn
+
+import pytest
+from multiprocess import Pipe
+
+from pedantic import in_subprocess
+from pedantic.decorators.fn_deco_in_subprocess import _inner, SubprocessError
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_simple():
+ @in_subprocess
+ def f() -> int:
+ return 42
+
+ assert await f() == 42
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_custom_object():
+ class Foo:
+ def __init__(self, v) -> None:
+ self._value = v
+
+ @in_subprocess
+ def f() -> Foo:
+ return Foo(v=42)
+
+ assert (await f())._value == 42
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_simple_async():
+ @in_subprocess
+ async def f() -> int:
+ return 42
+
+ assert await f() == 42
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_no_args():
+ @in_subprocess
+ def f() -> int:
+ time.sleep(0.1)
+ return 42
+
+ async def t() -> None:
+ for _ in range(6):
+ await asyncio.sleep(0.01)
+ nonlocal counter
+ counter += 1
+
+ counter = 0
+ task = asyncio.create_task(t())
+ assert await f() == 42
+ assert counter >= 5
+ await task
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_no_args_no_return():
+ @in_subprocess
+ def f() -> None:
+ time.sleep(0.1)
+
+ assert await f() is None
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_exception():
+ @in_subprocess
+ def f() -> NoReturn:
+ raise RuntimeError('foo')
+
+ with pytest.raises(expected_exception=RuntimeError):
+ await f()
+
+
+@pytest.mark.asyncio
+async def test_not_in_subprocess_blocks():
+ async def f() -> int:
+ time.sleep(0.1)
+ return 42
+
+ async def t() -> None:
+ for _ in range(6):
+ await asyncio.sleep(0.05)
+ nonlocal counter
+ counter += 1
+
+ counter = 0
+ task = asyncio.create_task(t())
+ assert await f() == 42
+ assert counter == 0
+ await task
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_with_arguments():
+ @in_subprocess
+ def f(a: int, b: int) -> int:
+ return a + b
+
+ assert await f(4, 5) == 9
+ assert await f(a=4, b=5) == 9
+
+
+def test_inner_function_sync():
+ """ Needed for line coverage"""
+
+ rx, tx = Pipe(duplex=False)
+ _inner(tx, lambda x: 1 / x, x=42)
+ assert rx.recv() == 1 / 42
+
+ _inner(tx, lambda x: 1 / x, x=0)
+ ex = rx.recv()
+ assert isinstance(ex, SubprocessError)
+
+
+def test_inner_function_async():
+ """ Needed for line coverage"""
+
+ async def foo(x):
+ return 1/x
+
+ rx, tx = Pipe(duplex=False)
+ _inner(tx, foo, x=42)
+ assert rx.recv() == 1 / 42
+
+ _inner(tx, foo, x=0)
+ ex = rx.recv()
+ assert isinstance(ex, SubprocessError)
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_instance_method():
+ class Foo:
+ async def pos_args(self) -> int:
+ return await self.f(4, 5)
+
+ async def kw_args(self) -> int:
+ return await self.f(a=4, b=5)
+
+ @in_subprocess
+ def f(self, a: int, b: int) -> int:
+ return a + b
+
+ foo = Foo()
+ assert await foo.pos_args() == 9
+ assert await foo.kw_args() == 9
+
+
+@pytest.mark.asyncio
+async def test_in_subprocess_static_method():
+ class Foo:
+ async def pos_args(self) -> int:
+ return await self.f(4, 5)
+
+ async def kw_args(self) -> int:
+ return await self.f(a=4, b=5)
+
+ @staticmethod
+ @in_subprocess
+ def f(a: int, b: int) -> int:
+ return a + b
+
+ foo = Foo()
+ assert await foo.pos_args() == 9
+ assert await foo.kw_args() == 9
diff --git a/tests/decorators/test_mock.py b/tests/decorators/test_mock.py
new file mode 100644
index 00000000..07c78546
--- /dev/null
+++ b/tests/decorators/test_mock.py
@@ -0,0 +1,10 @@
+from pedantic import mock
+
+
+def test_mock() -> None:
+ @mock(return_value=42)
+ def my_function(a, b, c):
+ return a + b + c
+
+ assert my_function(1, 2, 3) == 42
+ assert my_function(100, 200, 300) == 42
diff --git a/tests/decorators/test_rename_kwargs.py b/tests/decorators/test_rename_kwargs.py
new file mode 100644
index 00000000..3f5e9911
--- /dev/null
+++ b/tests/decorators/test_rename_kwargs.py
@@ -0,0 +1,15 @@
+from pedantic import rename_kwargs, Rename
+
+
+def test_rename_kwargs():
+ @rename_kwargs(
+ Rename(from_='x', to='a'),
+ Rename(from_='y', to='b'),
+ )
+ def operation(a: int, b: int) -> int:
+ return a + b
+
+ operation(4, 5)
+ operation(a=4, b=5)
+ operation(x=4, y=5)
+ operation(x=4, b=5)
diff --git a/tests/decorators/test_require_kwargs.py b/tests/decorators/test_require_kwargs.py
new file mode 100644
index 00000000..8b2feb09
--- /dev/null
+++ b/tests/decorators/test_require_kwargs.py
@@ -0,0 +1,29 @@
+import pytest
+
+from pedantic.exceptions import PedanticCallWithArgsException
+from pedantic import require_kwargs
+
+
+def test_kwargs():
+ @require_kwargs
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ calc(n=1, m=2, i=3)
+
+
+def test_no_kwargs_1():
+ @require_kwargs
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ calc(1, m=2, i=3)
+
+def test_no_kwargs_2():
+ @require_kwargs
+ def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ calc(1, 2, 3)
diff --git a/tests/decorators/test_retry_decorator.py b/tests/decorators/test_retry_decorator.py
new file mode 100644
index 00000000..eda7bd54
--- /dev/null
+++ b/tests/decorators/test_retry_decorator.py
@@ -0,0 +1,89 @@
+import pytest
+
+from pedantic import retry
+
+
+def test_retry_positive_no_args():
+ count = 0
+
+ @retry(attempts=5)
+ def foo():
+ nonlocal count
+ count += 1
+
+ foo()
+ assert count == 1
+
+
+def test_retry_positive_args_and_kwargs():
+ count = 0
+
+ @retry(attempts=5)
+ def foo(x, y):
+ nonlocal count
+ count += x + y
+
+ foo(12, y=42)
+ assert count == 54
+
+
+def test_retry_positive_no_args_fails_every_time():
+ count = 0
+
+ @retry(attempts=5)
+ def foo():
+ nonlocal count
+ count += 1
+ raise ValueError('foo')
+
+ with pytest.raises(ValueError):
+ foo()
+
+ assert count == 5
+
+
+def test_retry_positive_no_args_fails_different_exception_type():
+ count = 0
+
+ @retry(attempts=5, exceptions=AssertionError)
+ def foo():
+ nonlocal count
+ count += 1
+ raise ValueError('foo')
+
+ with pytest.raises(ValueError):
+ foo()
+
+ assert count == 1
+
+
+def test_retry_fails_same_exception_type():
+ count = 0
+
+ @retry(attempts=5, exceptions=AssertionError)
+ def foo():
+ nonlocal count
+ count += 1
+ raise AssertionError('foo')
+
+ with pytest.raises(AssertionError):
+ foo()
+
+ assert count == 5
+
+
+def test_retry_positive_no_args_fails_on_first_times():
+ count = 0
+
+ @retry(attempts=5)
+ def foo() -> int:
+ nonlocal count
+ count += 1
+
+ if count < 3:
+ raise ValueError('foo')
+
+ return count
+
+ assert foo() == 3
+ assert count == 3
diff --git a/tests/decorators/test_retry_func.py b/tests/decorators/test_retry_func.py
new file mode 100644
index 00000000..a42b2438
--- /dev/null
+++ b/tests/decorators/test_retry_func.py
@@ -0,0 +1,82 @@
+import pytest
+
+from pedantic.decorators.fn_deco_retry import retry_func
+
+def test_retry_positive_no_args():
+ count = 0
+
+ def foo():
+ nonlocal count
+ count += 1
+
+ retry_func(func=foo, attempts=5)
+ assert count == 1
+
+
+def test_retry_positive_args_and_kwargs():
+ count = 0
+
+ def foo(x, y):
+ nonlocal count
+ count += x + y
+
+ retry_func(foo, 12, attempts=5, y=42)
+ assert count == 54
+
+
+def test_retry_positive_no_args_fails_every_time():
+ count = 0
+
+ def foo():
+ nonlocal count
+ count += 1
+ raise ValueError('foo')
+
+ with pytest.raises(ValueError):
+ retry_func(func=foo, attempts=5)
+
+ assert count == 5
+
+
+def test_retry_positive_no_args_fails_different_exception_type():
+ count = 0
+
+ def foo():
+ nonlocal count
+ count += 1
+ raise ValueError('foo')
+
+ with pytest.raises(ValueError):
+ retry_func(func=foo, attempts=5, exceptions=AssertionError)
+
+ assert count == 1
+
+
+def test_retry_fails_same_exception_type():
+ count = 0
+
+ def foo():
+ nonlocal count
+ count += 1
+ raise AssertionError('foo')
+
+ with pytest.raises(AssertionError):
+ retry_func(func=foo, attempts=5, exceptions=AssertionError)
+
+ assert count == 5
+
+
+def test_retry_positive_no_args_fails_on_first_times():
+ count = 0
+
+ def foo() -> int:
+ nonlocal count
+ count += 1
+
+ if count < 3:
+ raise ValueError('foo')
+
+ return count
+
+ assert retry_func(func=foo, attempts=5) == 3
+ assert count == 3
diff --git a/tests/decorators/test_small_method_decorators.py b/tests/decorators/test_small_method_decorators.py
new file mode 100644
index 00000000..b0f98c55
--- /dev/null
+++ b/tests/decorators/test_small_method_decorators.py
@@ -0,0 +1,311 @@
+import asyncio
+import warnings
+from abc import abstractmethod
+
+import pytest
+
+from pedantic import overrides, timer, count_calls, trace, trace_if_returns, does_same_as_function, deprecated, \
+ unimplemented, mock, require_kwargs
+from pedantic.exceptions import NotImplementedException, PedanticOverrideException, PedanticCallWithArgsException
+
+
+def test_overrides_parent_has_no_such_method():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ def operation(self): pass
+
+
+def test_overrides_all_good():
+ class MyClassA:
+ def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ def operation(self):
+ return 42
+
+ b = MyClassB()
+ b.operation()
+
+
+def test_overrides_static_method():
+ class MyClassA:
+ @staticmethod
+ def operation(): pass
+
+ class MyClassB(MyClassA):
+ @staticmethod
+ @overrides(MyClassA)
+ def operation():
+ return 42
+
+ b = MyClassB()
+ assert b.operation() == 42
+ assert MyClassB.operation() == 42
+
+
+def test_overrides_below_property():
+ class MyClassA:
+ @property
+ @abstractmethod
+ def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @property
+ @overrides(MyClassA) # Note: it does not work the other way around
+ def operation(self):
+ return 43
+
+ b = MyClassB()
+ assert b.operation == 43
+
+
+def test_overrides_function():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ @overrides(MyClassA)
+ def operation(): return 42
+
+
+def test_deprecated_1():
+ @deprecated
+ def old_method(i: int) -> str: return str(i)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ old_method(42)
+ assert len(w) == 1
+ assert issubclass(w[-1].category, DeprecationWarning)
+ assert "deprecated" in str(w[-1].message)
+
+
+def test_deprecated_2():
+ def old_method(i: int) -> str:
+ return str(i)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ old_method(42)
+ assert not len(w) == 1
+
+
+def test_unimplemented():
+ @unimplemented
+ def dirt(i: int) -> str:
+ return str(i)
+
+ with pytest.raises(expected_exception=NotImplementedException):
+ dirt(42)
+
+
+def test_timer():
+ @timer
+ def operation(i: int) -> str:
+ return str(i)
+
+ operation(42)
+
+
+def test_count_calls():
+ @count_calls
+ def operation(i: int) -> str:
+ return str(i)
+
+ operation(42)
+
+
+def test_trace():
+ def some_method(x, y):
+ return x + y
+
+ traced_method = trace(some_method)
+ assert some_method(42, 99) == traced_method(42, 99)
+
+
+def test_trace_if_returns():
+ def some_method(x, y):
+ return x + y
+
+ traced_method = trace_if_returns(100)(some_method)
+ assert some_method(42, 99) == traced_method(42, 99)
+ assert some_method(42, 58) == traced_method(42, 58)
+
+
+def test_does_same_as_function():
+ def some_method(x, y, z):
+ return x * (y + z)
+
+ @does_same_as_function(some_method)
+ def other_method(x, y, z):
+ return x * y + x * z
+
+ other_method(1, 2, 3)
+ other_method(4, 5, 6)
+
+
+def test_does_same_as_function_wrong():
+ def some_method(x, y, z):
+ return x * (y + z)
+
+ @does_same_as_function(some_method)
+ def other_method(x, y, z):
+ return x * y + z
+
+ other_method(0, 2, 0)
+ with pytest.raises(expected_exception=AssertionError):
+ other_method(4, 5, 6)
+
+
+@pytest.mark.asyncio
+async def test_overrides_async_instance_method() -> None:
+ class MyClassA:
+ async def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ async def operation(self):
+ await asyncio.sleep(0)
+ return 42
+
+ b = MyClassB()
+ await b.operation()
+
+
+@pytest.mark.asyncio
+async def test_overrides_parent_has_no_such_method_async():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ async def operation(self): return 42
+
+
+@pytest.mark.asyncio
+async def test_count_calls_async():
+ @count_calls
+ async def operation(i: int) -> str:
+ await asyncio.sleep(0)
+ return str(i)
+
+ res = await operation(42)
+ assert res == '42'
+
+
+@pytest.mark.asyncio
+async def test_trace_async():
+ async def some_method(x, y):
+ await asyncio.sleep(0)
+ return x + y
+
+ traced_method = trace(some_method)
+ assert await some_method(42, 99) == await traced_method(42, 99)
+
+
+@pytest.mark.asyncio
+async def test_trace_if_returns_async():
+ async def some_method(x, y):
+ await asyncio.sleep(0)
+ return x + y
+
+ traced_method = trace_if_returns(100)(some_method)
+ assert await some_method(42, 99) == await traced_method(42, 99)
+ assert await some_method(42, 58), await traced_method(42, 58)
+
+
+@pytest.mark.asyncio
+async def test_timer_async():
+ @timer
+ async def operation(i: int) -> str:
+ await asyncio.sleep(0.05)
+ return str(i)
+
+ await operation(42)
+
+
+@pytest.mark.asyncio
+async def test_deprecated_async():
+ @deprecated
+ async def old_method(i: int) -> str:
+ return str(i)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ await old_method(42)
+ assert len(w) == 1
+ assert issubclass(w[-1].category, DeprecationWarning)
+ assert "deprecated" in str(w[-1].message)
+
+
+@pytest.mark.asyncio
+async def test_does_same_as_function_async():
+ async def some_method(x, y, z):
+ await asyncio.sleep(0)
+ return x * (y + z)
+
+ @does_same_as_function(some_method)
+ async def other_method(x, y, z):
+ await asyncio.sleep(0)
+ return x * y + x * z
+
+ await other_method(1, 2, 3)
+ await other_method(4, 5, 6)
+
+
+@pytest.mark.asyncio
+async def test_does_same_as_function_async_and_sync():
+ def some_method(x, y, z):
+ return x * (y + z)
+
+ @does_same_as_function(some_method)
+ async def other_method(x, y, z):
+ await asyncio.sleep(0)
+ return x * y + x * z
+
+ await other_method(1, 2, 3)
+ await other_method(4, 5, 6)
+
+
+@pytest.mark.asyncio
+async def test_does_same_as_function_wrong():
+ async def some_method(x, y, z):
+ await asyncio.sleep(0)
+ return x * (y + z)
+
+ @does_same_as_function(some_method)
+ async def other_method(x, y, z):
+ await asyncio.sleep(0)
+ return x * y + z
+
+ await other_method(0, 2, 0)
+
+ with pytest.raises(expected_exception=AssertionError):
+ await other_method(4, 5, 6)
+
+
+@pytest.mark.asyncio
+async def test_mock_async():
+ @mock(return_value=42)
+ async def my_function(a, b, c): return a + b + c
+
+ assert await my_function(1, 2, 3) == 42
+ assert await my_function(100, 200, 300) == 42
+
+
+@pytest.mark.asyncio
+async def test_require_kwargs():
+ @require_kwargs
+ async def calc(n: int, m: int, i: int) -> int:
+ return n + m + i
+
+ await calc(n=1, m=2, i=3)
+
+ with pytest.raises(expected_exception=PedanticCallWithArgsException):
+ await calc(1, m=2, i=3)
diff --git a/tests/decorators/validate/__init__.py b/tests/decorators/validate/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/decorators/validate/test_convert_value.py b/tests/decorators/validate/test_convert_value.py
new file mode 100644
index 00000000..c5ebfa88
--- /dev/null
+++ b/tests/decorators/validate/test_convert_value.py
@@ -0,0 +1,56 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ConversionError
+from pedantic.decorators.fn_deco_validate.convert_value import convert_value
+
+
+def test_convert_to_bool():
+ for value in [True, 1, '1', ' 1 ', ' tRuE ', 'TRUE']:
+ assert convert_value(value=value, target_type=bool) is True
+
+ for value in [False, 0, '0', ' 0 ', ' fAlSe ', 'FALSE']:
+ assert convert_value(value=value, target_type=bool) is False
+
+ for value in ['alse', 0.1, '0.2', ' 0000 ', 'Talse', 'Frue', 42]:
+ with pytest.raises(expected_exception=ConversionError):
+ convert_value(value=value, target_type=bool)
+
+
+def test_convert_to_int():
+ for value in range(-4, 4):
+ assert convert_value(value=value, target_type=int) == value
+
+ assert convert_value(value='42', target_type=int) == 42
+ assert convert_value(value=' 0000 ', target_type=int) == 0
+
+ for value in ['alse', 'Talse', 'Frue', 0.2, '0.2']:
+ with pytest.raises(expected_exception=ConversionError):
+ convert_value(value=value, target_type=int)
+
+
+def test_convert_to_float():
+ for value in range(-4, 4):
+ assert convert_value(value=value, target_type=float) == value
+
+ assert convert_value(value=0.2, target_type=float) == 0.2
+ assert convert_value(value='0.2', target_type=float) == 0.2
+ assert convert_value(value='42', target_type=float) == 42
+ assert convert_value(value=' 0000 ', target_type=float) == 0
+
+ for value in ['alse', 'Talse', 'Frue']:
+ with pytest.raises(expected_exception=ConversionError):
+ convert_value(value=value, target_type=float)
+
+
+def test_convert_to_list():
+ for value in [[], [1], ['1', ' 1 '], [' tRuE ', 'TRUE']]:
+ assert convert_value(value=value, target_type=list) == value
+
+ assert convert_value(value='1,2,3', target_type=list) == ['1', '2', '3']
+
+
+def test_convert_to_dict():
+ for value in [{}, {1: 2}, {'1': ' 1 '}, {1: ' tRuE ', 2: 'TRUE'}]:
+ assert convert_value(value=value, target_type=dict) == value
+
+ assert convert_value(value='1:1,2:4,3:7', target_type=dict) == {'1': '1', '2': '4', '3': '7'}
diff --git a/tests/decorators/validate/test_datetime_isoformat.py b/tests/decorators/validate/test_datetime_isoformat.py
new file mode 100644
index 00000000..d0935d08
--- /dev/null
+++ b/tests/decorators/validate/test_datetime_isoformat.py
@@ -0,0 +1,23 @@
+from datetime import datetime, timedelta
+
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import DatetimeIsoFormat
+
+
+def test_validator_datetime_iso_format():
+ @validate(Parameter(name='x', validators=[DatetimeIsoFormat()]))
+ def foo(x):
+ return x
+
+ now = datetime.now()
+ assert abs(now - foo(now.isoformat()) )< timedelta(milliseconds=1)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo('12.12.2020')
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo('invalid')
diff --git a/tests/decorators/validate/test_flask_parameters.py b/tests/decorators/validate/test_flask_parameters.py
new file mode 100644
index 00000000..b8573469
--- /dev/null
+++ b/tests/decorators/validate/test_flask_parameters.py
@@ -0,0 +1,628 @@
+import json
+from dataclasses import dataclass
+from typing import List, Dict, Any
+
+import pytest
+from flask import Flask, Response, jsonify
+
+from pedantic import Email, overrides
+from pedantic.decorators.fn_deco_validate.exceptions import ValidateException, TooManyArguments, \
+ ParameterException, ExceptionDictKey, InvalidHeader
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, ReturnAs
+from pedantic.decorators.fn_deco_validate.parameters.flask_parameters import FlaskJsonParameter, FlaskFormParameter, \
+ FlaskHeaderParameter, FlaskGetParameter, FlaskPathParameter, Deserializable, GenericFlaskDeserializer
+from pedantic.decorators.fn_deco_validate.validators import NotEmpty, Min
+
+JSON = 'application/json'
+OK = 200
+INVALID = 422
+TOO_MANY_ARGS = 400
+
+
+def test_validator_flask_json():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(
+ FlaskJsonParameter(name='key', validators=[NotEmpty()]),
+ )
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.route('/required')
+ @validate(
+ FlaskJsonParameter(name='required', required=True),
+ FlaskJsonParameter(name='not_required', required=False),
+ FlaskJsonParameter(name='not_required_with_default', required=False, default=42),
+ )
+ def required_params(required, not_required, not_required_with_default) -> Response:
+ return jsonify({
+ 'required': required,
+ 'not_required': not_required,
+ 'not_required_with_default': not_required_with_default,
+ })
+
+ @app.route('/types')
+ @validate(
+ FlaskJsonParameter(name='bool_param', value_type=bool),
+ FlaskJsonParameter(name='int_param', value_type=int),
+ FlaskJsonParameter(name='float_param', value_type=float),
+ FlaskJsonParameter(name='str_param', value_type=str),
+ FlaskJsonParameter(name='list_param', value_type=list),
+ FlaskJsonParameter(name='dict_param', value_type=dict),
+ )
+ def different_types(
+ bool_param,
+ int_param,
+ float_param,
+ str_param,
+ list_param,
+ dict_param,
+ ) -> Response:
+ return jsonify({
+ 'bool_param': bool_param,
+ 'int_param': int_param,
+ 'float_param': float_param,
+ 'str_param': str_param,
+ 'list_param': list_param,
+ 'dict_param': dict_param,
+ })
+
+ @app.route('/args')
+ @validate(
+ FlaskJsonParameter(name='a', validators=[NotEmpty()]),
+ FlaskJsonParameter(name='b', validators=[NotEmpty()]),
+ return_as=ReturnAs.ARGS,
+ )
+ def names_do_not_need_to_match(my_key: str, another: str) -> Response:
+ return jsonify({
+ 'my_key': my_key,
+ 'another': another,
+ })
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ @app.errorhandler(TooManyArguments)
+ def handle_validation_error(exception: TooManyArguments) -> Response:
+ print(str(exception))
+ response = jsonify(str(exception))
+ response.status_code = TOO_MANY_ARGS
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/', data=json.dumps({'key': ' hello world '}), content_type=JSON)
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/', data=json.dumps({'key': ' '}), content_type=JSON)
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: 'NotEmpty',
+ ExceptionDictKey.VALUE: ' ',
+ ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+ data = {
+ 'key': ' hello world ',
+ 'required': '1',
+ }
+ res = client.get('/', data=json.dumps(data), content_type=JSON)
+ assert res.status_code == TOO_MANY_ARGS
+ assert res.json == "Got unexpected arguments: ['required']"
+
+
+def test_validator_flask_form_data():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', validators=[NotEmpty()]))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/', data={'key': ' hello world '})
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/', data={'key': ' '})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: 'NotEmpty',
+ ExceptionDictKey.VALUE: ' ',
+ ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+
+def test_validator_flask_header():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskHeaderParameter(name='key', validators=[NotEmpty()]))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(InvalidHeader)
+ def handle_validation_error(exception: InvalidHeader) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/', headers={'key': ' hello world '}, data={})
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/', headers={'key': ' '}, data={})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALUE: ' ',
+ ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
+ ExceptionDictKey.PARAMETER: 'key',
+ ExceptionDictKey.VALIDATOR: 'NotEmpty',
+ }
+ assert res.json == expected
+
+
+def test_validator_flask_header_optional_parameter():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskHeaderParameter(name='key', validators=[NotEmpty()], required=False))
+ def hello_world(key: str = None) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get('/', headers={'key': ' hello world '}, data={})
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/', headers={}, data={})
+ assert res.status_code == OK
+ assert res.json is None
+
+
+def test_validator_flask_get():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskGetParameter(name='key', value_type=str, validators=[NotEmpty()]))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/?key=hello_world', data={})
+ assert res.status_code == OK
+ assert res.json == 'hello_world'
+
+ res = client.get('/?key=hello world', data={})
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/?key= ', data={})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: 'NotEmpty',
+ ExceptionDictKey.VALUE: ' ',
+ ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+
+def test_validator_flask_get_multiple_values_for_same_key():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskGetParameter(name='key', value_type=list, validators=[NotEmpty()]))
+ def hello_world(key: List[str]) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get('/?key=hello&key=world', data={})
+ assert res.status_code == OK
+ assert res.json == ['hello', 'world']
+
+
+def test_validator_flask_path():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskPathParameter(name='key', validators=[NotEmpty()]))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/hello_world', data={})
+ assert res.status_code == OK
+ assert res.json == 'hello_world'
+
+ res = client.get('/hello world', data={})
+ assert res.status_code == OK
+ assert res.json == 'hello world'
+
+ res = client.get('/ ', data={})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: 'NotEmpty',
+ ExceptionDictKey.VALUE: ' ',
+ ExceptionDictKey.MESSAGE: 'Got empty String which is invalid.',
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+
+def test_invalid_value_type():
+ app = Flask(__name__)
+
+ with pytest.raises(expected_exception=AssertionError):
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', value_type=tuple))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+
+def test_wrong_name():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='k', value_type=str))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ValidateException)
+ def handle_validation_error(exception: ValidateException) -> Response:
+ print(str(exception))
+ response = jsonify(str(exception))
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get(data={'key': 'k'})
+ assert res.status_code == INVALID
+ assert 'Value for parameter k is required.' in res.json
+
+
+def test_default_value():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', value_type=str, default='42'))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get(data={})
+ assert res.status_code == OK
+ assert res.json == '42'
+
+
+def test_not_required_allows_none_kwargs_without_none():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', value_type=str, required=False),
+ return_as=ReturnAs.KWARGS_WITHOUT_NONE)
+ def hello_world(key: str = 'it works') -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get(data={})
+ assert res.status_code == OK
+ assert res.json == 'it works'
+
+
+def test_not_required_allows_none_kwargs_with_none():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', value_type=str, required=False, default=None),
+ return_as=ReturnAs.KWARGS_WITH_NONE)
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get(data={'key': None})
+ assert res.status_code == OK
+ assert res.json is None
+
+
+def test_not_required_with_default():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskFormParameter(name='key', value_type=str, required=False, default='42'))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get(data={})
+ assert res.status_code == OK
+ assert res.json == '42'
+
+
+def test_validator_flask_path_type_conversion():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskPathParameter(name='key', value_type=int, validators=[Min(42)]))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/42', data={})
+ assert res.status_code == OK
+ assert res.json == 42
+
+ res = client.get('/42f', data={})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: None,
+ ExceptionDictKey.VALUE: '42f',
+ ExceptionDictKey.MESSAGE: "Value 42f cannot be converted to .",
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+
+def test_validator_flask_json_parameter_does_not_get_json():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskJsonParameter(name='key'))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ print(str(exception))
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get('/', data={})
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.VALIDATOR: None,
+ ExceptionDictKey.VALUE: 'None',
+ ExceptionDictKey.MESSAGE: 'Value for parameter key is required.',
+ ExceptionDictKey.PARAMETER: 'key',
+ }
+ assert res.json == expected
+
+
+def test_validator_flask_json_parameter_does_not_get_json_but_default():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskJsonParameter(name='key', default='42'))
+ def hello_world(key: str) -> Response:
+ return jsonify(key)
+
+ with app.test_client() as client:
+ res = client.get('/', data={})
+ assert res.status_code == OK
+ assert res.json == '42'
+
+
+def test_too_many_arguments():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskJsonParameter(name='k', value_type=str))
+ def hello_world(**kwargs) -> Response:
+ return jsonify(str(kwargs))
+
+ @app.errorhandler(TooManyArguments)
+ def handle_validation_error(exception: TooManyArguments) -> Response:
+ print(str(exception))
+ response = jsonify(str(exception))
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get(data=json.dumps({'k': 'k', 'a': 1}), content_type=JSON)
+ assert res.status_code == INVALID
+ assert res.json == "Got unexpected arguments: ['a']"
+
+
+def test_exception_for_required_parameter():
+ app = Flask(__name__)
+ key = 'PASSWORD'
+
+ @app.route('/')
+ @validate(FlaskJsonParameter(name=key, value_type=str))
+ def hello_world(**kwargs) -> Response:
+ return jsonify(str(kwargs))
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ reason = 'required' if 'required' in exception.message else 'invalid'
+ response = jsonify({exception.parameter_name: [{'KEY': reason}]})
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get(data=json.dumps({}), content_type=JSON)
+ assert res.status_code == INVALID
+ assert res.json == {key: [{'KEY': 'required'}]}
+
+
+def test_async_endpoints():
+ app = Flask(__name__)
+
+ @app.route('/')
+ @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]))
+ async def hello_world(k) -> Response:
+ return jsonify(str(k))
+
+ @app.route('/foo/')
+ @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]), return_as=ReturnAs.ARGS)
+ async def return_args(k) -> Response:
+ return jsonify(str(k))
+
+ @app.route('/bar/')
+ @validate(FlaskPathParameter(name='k', value_type=int, validators=[Min(42)]),
+ return_as=ReturnAs.KWARGS_WITH_NONE)
+ async def return_kwargs_with_none(k) -> Response:
+ return jsonify(str(k))
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ res = client.get(path=f'/45')
+ assert res.status_code == OK
+
+ res = client.get(path=f'/foo/45')
+ assert res.status_code == OK
+
+ res = client.get(path=f'/bar/45')
+ assert res.status_code == OK
+
+ res = client.get(path=f'/41')
+ assert res.status_code == INVALID
+ expected = {
+ ExceptionDictKey.MESSAGE: 'smaller then allowed: 41 is not >= 42',
+ ExceptionDictKey.PARAMETER: 'k',
+ ExceptionDictKey.VALIDATOR: 'Min',
+ ExceptionDictKey.VALUE: '41',
+ }
+ assert res.json == expected
+
+
+def test_json_parameter_with_default_value():
+ app = Flask(__name__)
+
+ @app.route('/testing/message/', methods=['POST'])
+ @validate(
+ FlaskPathParameter(name='email', value_type=str, validators=[Email()]),
+ FlaskJsonParameter(name='content', value_type=str, default='this is a fake message', required=False),
+ )
+ def testing_send_system_message(email: str, content: str) -> Response:
+ return jsonify({'email': email, 'content': content})
+
+ with app.test_client() as client:
+ res = client.post(path=f'/testing/message/adam@eva.de')
+ assert res.status_code == OK
+ expected = {
+ 'email': 'adam@eva.de',
+ 'content': 'this is a fake message',
+ }
+ assert res.json == expected
+
+ res = client.post(path=f'/testing/message/adam@eva.de', json={'content': 'hello world'})
+ assert res.status_code == OK
+ expected = {
+ 'email': 'adam@eva.de',
+ 'content': 'hello world',
+ }
+ assert res.json == expected
+
+
+def test_generic_deserializer():
+ @dataclass(frozen=True)
+ class User(Deserializable):
+ firstname: str
+ lastname: str
+ age: int
+
+ @staticmethod
+ @overrides(Deserializable)
+ def from_json(data: Dict[str, Any]) -> 'User':
+ return User(
+ firstname=data['firstname'],
+ lastname=data['lastname'],
+ age=Min(value=18).validate_param(value=int(data['age']), parameter_name='age'),
+ )
+
+ app = Flask(__name__)
+
+ @app.route('/foo', methods=['POST'])
+ @validate(
+ GenericFlaskDeserializer(name='user', cls=User, catch_exception=True),
+ )
+ def the_generic_approach(user: User) -> Response:
+ return jsonify({'name': user.firstname})
+
+ @app.route('/bar', methods=['POST'])
+ @validate(
+ GenericFlaskDeserializer(name='user', cls=User, catch_exception=False),
+ )
+ def do_not_catch(user: User) -> Response:
+ return jsonify({'name': user.firstname})
+
+ @app.errorhandler(ParameterException)
+ def handle_validation_error(exception: ParameterException) -> Response:
+ response = jsonify(exception.to_dict)
+ response.status_code = INVALID
+ return response
+
+ with app.test_client() as client:
+ data = {
+ 'firstname': 'Albert',
+ 'lastname': 'Einstein',
+ 'age': '56',
+ }
+ res = client.post(path=f'/foo', json=data)
+ assert res.status_code == 200
+ assert res.json == {'name': 'Albert'}
+
+ data.pop('age')
+ res = client.post(path=f'/foo', json=data)
+ assert res.status_code == 422
+
+ res = client.post(path=f'/bar', json=data)
+ assert res.status_code == 500
+
+ data['age'] = '16'
+ res = client.post(path=f'/foo', json=data)
+ assert res.status_code == 422
+ expected = {
+ ExceptionDictKey.MESSAGE: 'smaller then allowed: 16 is not >= 18',
+ ExceptionDictKey.PARAMETER: 'age',
+ ExceptionDictKey.VALIDATOR: 'Min',
+ ExceptionDictKey.VALUE: '16',
+ }
+ assert res.json == expected
diff --git a/tests/decorators/validate/test_parameter_environment_variable.py b/tests/decorators/validate/test_parameter_environment_variable.py
new file mode 100644
index 00000000..c79b16ff
--- /dev/null
+++ b/tests/decorators/validate/test_parameter_environment_variable.py
@@ -0,0 +1,87 @@
+import os
+
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import EnvironmentVariableParameter
+
+
+def test_parameter_environment_variable_str():
+ @validate(EnvironmentVariableParameter(name='foo', value_type=str))
+ def bar(foo):
+ return foo
+
+ os.environ['foo'] = '42'
+ assert bar() == '42'
+
+
+def test_parameter_environment_variable_int():
+ @validate(EnvironmentVariableParameter(name='foo', value_type=int))
+ def bar(foo):
+ return foo
+
+ os.environ['foo'] = '42'
+ assert bar() == 42
+
+
+def test_parameter_environment_variable_float():
+ @validate(EnvironmentVariableParameter(name='foo', value_type=float))
+ def bar(foo):
+ return foo
+
+ os.environ['foo'] = '42.7'
+ assert bar() == 42.7
+
+
+def test_parameter_environment_variable_bool():
+ @validate(EnvironmentVariableParameter(name='foo', value_type=bool))
+ def bar(foo):
+ return foo
+
+ for value in ['true', 'True', 'TRUE']:
+ os.environ['foo'] = value
+ assert bar() is True
+
+ for value in ['false', 'False', 'FALSE']:
+ os.environ['foo'] = value
+ assert bar() is False
+
+ for value in ['invalid', 'frue', 'talse']:
+ os.environ['foo'] = value
+
+ with pytest.raises(expected_exception=ParameterException):
+ bar()
+
+def test_parameter_environment_variable_not_set():
+ @validate(EnvironmentVariableParameter(name='foo'))
+ def bar(foo):
+ return foo
+
+ with pytest.raises(expected_exception=ParameterException):
+ bar()
+
+
+def test_invalid_value_type():
+ with pytest.raises(expected_exception=AssertionError):
+ @validate(EnvironmentVariableParameter(name='foo', value_type=dict))
+ def bar(foo):
+ return foo
+
+
+def test_parameter_environment_variable_different_name():
+ @validate(EnvironmentVariableParameter(name='foo', env_var_name='fuu', value_type=str))
+ def bar(foo):
+ return foo
+
+ os.environ['fuu'] = '42'
+ assert bar() == '42'
+
+
+def test_two_parameters():
+ @validate(EnvironmentVariableParameter(name='a'), strict=False)
+ def foo(a: float, b: int) -> str:
+ return f'{a} and {b}'
+
+ os.environ['a'] = '42'
+ assert foo(b=42) == '42 and 42'
diff --git a/tests/decorators/validate/test_validate.py b/tests/decorators/validate/test_validate.py
new file mode 100644
index 00000000..ba7ebe86
--- /dev/null
+++ b/tests/decorators/validate/test_validate.py
@@ -0,0 +1,453 @@
+import os
+from datetime import datetime
+from typing import Optional, Any
+
+import pytest
+
+from pedantic import DateTimeUnixTimestamp
+from pedantic.decorators.fn_deco_validate.exceptions import ValidateException, ParameterException, \
+ValidatorException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, ReturnAs
+from pedantic.decorators.fn_deco_validate.parameters import Parameter, EnvironmentVariableParameter
+from pedantic.decorators.fn_deco_validate.validators import MaxLength, Min, Max, Email, Validator
+
+
+def test_single_validator():
+ validator = MaxLength(3)
+ converted_value = validator.validate(value='hed')
+ assert 'hed' == converted_value
+
+ with pytest.raises(expected_exception=ValidatorException) as ex:
+ validator.validate(value='hello world')
+
+ expected_error_msg = 'MaxLength: hello world is too long with length 11. Value: hello world'
+ assert str(ex.value) == expected_error_msg
+
+
+def test_single_parameter():
+ parameter = Parameter(name='x', validators=[MaxLength(3)])
+ converted_value = parameter.validate(value='hed')
+ assert 'hed' == converted_value
+
+ with pytest.raises(expected_exception=ParameterException):
+ parameter.validate(value='hello world')
+
+
+def test_multiple_parameters():
+ @validate(
+ Parameter(name='a', validators=[Min(3)]),
+ Parameter(name='b', validators=[Max(3)]),
+ Parameter(name='c', validators=[Max(43)]),
+ )
+ def bar(a, b, c):
+ return a + b + c
+
+ assert bar(3, 3, 5) == 11
+ assert bar(a=3, b=3, c=5) == 11
+
+
+def test_validate_args():
+ @validate(
+ Parameter(name='a', validators=[Min(42, include_boundary=False)]),
+ Parameter(name='b', validators=[Min(42, include_boundary=False)]),
+ Parameter(name='c', validators=[Min(42, include_boundary=False)]),
+ )
+ def some_calculation(a, b, c):
+ return a + b + c
+
+ some_calculation(43, 45, 50)
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(30, 40, 50)
+ with pytest.raises(expected_exception=ParameterException):
+ some_calculation(c=30, a=40, b=50)
+
+
+def test_validate_instance_method():
+ class MyClass:
+ @validate(
+ Parameter(name='x', validators=[Min(1)]),
+ )
+ def some_calculation(self, x: int) -> int:
+ return x
+
+ @validate(
+ Parameter(name='x', validators=[Min(1)]),
+ return_as=ReturnAs.KWARGS_WITHOUT_NONE,
+ )
+ def some_calculation_2(self, x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(1)
+ m.some_calculation(42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(0)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(-42)
+
+ m.some_calculation_2(1)
+ m.some_calculation_2(42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation_2(0)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation_2(-42)
+
+
+def test_validate_static_method():
+ """ The @staticmethod decorator have to be ABOVE the @validate decorator. """
+
+ class MyClass:
+ @staticmethod
+ @validate(
+ Parameter(name='x', validators=[Min(1)]),
+ )
+ def some_calculation(x: int) -> int:
+ return x
+
+ m = MyClass()
+ m.some_calculation(1)
+ m.some_calculation(42)
+
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(0)
+ with pytest.raises(expected_exception=ParameterException):
+ m.some_calculation(-42)
+
+
+def test_less_parameter_than_arguments():
+ @validate(
+ Parameter(name='b'),
+ strict=False,
+ )
+ def some_calculation(a, b, c):
+ return a + b + c
+
+ some_calculation(43, 0, -50)
+
+ with pytest.raises(expected_exception=ValidateException):
+ some_calculation(30, None, 50)
+
+
+def test_empty_parameter_kwargs_with_none():
+ @validate(
+ Parameter(name='a', required=False),
+ Parameter(name='b', required=True),
+ Parameter(name='c', required=False),
+ return_as=ReturnAs.KWARGS_WITH_NONE
+ )
+ def some_calculation(a, b, c):
+ return str(a) + str(b) + str(c)
+
+ assert some_calculation(43, 0, -50) == '430-50'
+ assert some_calculation(None, 0, None) == 'None0None'
+
+
+def test_empty_parameter_kwargs_without_none():
+ @validate(
+ Parameter(name='a', required=False),
+ Parameter(name='b', required=True),
+ Parameter(name='c', required=False),
+ return_as=ReturnAs.KWARGS_WITHOUT_NONE
+ )
+ def some_calculation(a: Optional[int] = 1, b: Optional[int] = 2, c: Optional[int] = 3):
+ return str(a) + str(b) + str(c)
+
+ assert some_calculation(43, 0, -50) == '430-50'
+ assert some_calculation(None, 0, None) == '103'
+
+
+def test_required():
+ @validate(
+ Parameter(name='a', required=True),
+ Parameter(name='b', required=True),
+ Parameter(name='c', required=True),
+ )
+ def some_calculation(a, b, c):
+ return a + b + c
+
+ assert some_calculation(43, 0, -50) == -7
+
+ with pytest.raises(expected_exception=ValidateException):
+ some_calculation(30, None, 50)
+
+
+def test_call_with_args():
+ @validate(
+ Parameter(name='x', validators=[Min(1)]),
+ )
+ def some_calculation(x: int) -> int:
+ return x
+
+ assert some_calculation(42) == 42
+
+
+def test_external_parameter_accepts_value_when_given():
+ @validate(EnvironmentVariableParameter(name='foo'))
+ def bar(foo):
+ return foo
+
+ assert bar('42') == '42'
+ assert bar(foo='42') == '42'
+
+
+def test_external_parameter_ignores_value_when_given():
+ @validate(EnvironmentVariableParameter(name='foo'), ignore_input=True)
+ def bar(foo):
+ return foo
+
+ os.environ['foo'] = '1'
+
+ assert bar('42') == '1'
+ assert bar(foo='42') == '1'
+
+
+def test_external_parameter_mixed_with_normal_parameter():
+ @validate(
+ EnvironmentVariableParameter(name='foo'),
+ Parameter(name='footer'),
+ return_as=ReturnAs.KWARGS_WITHOUT_NONE,
+ )
+ def bar(foo, footer):
+ return foo, footer
+
+ assert bar('42', 3) == ('42', 3)
+
+ os.environ['foo'] = '42'
+ assert bar(footer=3) == ('42', 3)
+
+
+def test_too_many_arguments():
+ @validate(
+ Parameter(name='x'),
+ )
+ def bar(x):
+ return x
+
+ assert bar(42) == 42
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(42, 43)
+
+
+def test_unexpected_parameter_strict():
+ @validate(Parameter(name='y'))
+ def bar(x):
+ return x
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(42)
+ with pytest.raises(expected_exception=ValidateException):
+ bar(x=42)
+
+
+def test_unexpected_parameter_not_strict():
+ @validate(Parameter(name='y'), strict=False)
+ def bar(x):
+ return x
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(42)
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(x=42)
+
+
+def test_unexpected_parameter_not_strict_external():
+ @validate(EnvironmentVariableParameter(name='foo'))
+ def bar(x):
+ return x
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(42)
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar(x=42)
+
+
+def test_return_as_simple():
+ @validate(Parameter(name='x'), return_as=ReturnAs.ARGS)
+ def bar(x):
+ return x
+
+ assert bar(42) == 42
+ assert bar(x=42) == 42
+
+
+def test_return_as_args():
+ @validate(Parameter(name='x'), return_as=ReturnAs.ARGS)
+ def bar(*args, **kwargs):
+ return args, kwargs
+
+ assert bar(42) == ((42,), {})
+ assert bar(x=42) == ((42,), {})
+
+
+def test_return_as_kwargs_with_none():
+ @validate(Parameter(name='x'), return_as=ReturnAs.KWARGS_WITH_NONE)
+ def bar(*args, **kwargs):
+ return args, kwargs
+
+ assert bar(42) == ((), {'x': 42})
+ assert bar(x=42) == ((), {'x': 42})
+
+
+def test_return_as_kwargs_without_none():
+ @validate(Parameter(name='x'), return_as=ReturnAs.KWARGS_WITHOUT_NONE)
+ def bar(*args, **kwargs):
+ return args, kwargs
+
+ assert bar(42) == ((), {'x': 42})
+ assert bar(x=42) == ((), {'x': 42})
+
+
+def test_return_as_args_advanced():
+ @validate(
+ Parameter(name='a'),
+ Parameter(name='b'),
+ Parameter(name='c'),
+ return_as=ReturnAs.ARGS,
+ )
+ def bar(a, b, *args, **kwargs):
+ return a, b, args, kwargs
+
+ bar(a=1, b=3, c=42)
+ bar(1, 3, 4)
+ bar(1, 3, c=4)
+
+def test_return_as_args_advanced_different_order():
+ @validate(
+ Parameter(name='c'),
+ Parameter(name='a'),
+ Parameter(name='b'),
+ return_as=ReturnAs.ARGS,
+ )
+ def bar(a, b, *args, **kwargs):
+ return a, b, args, kwargs
+
+ assert bar(a=1, b=3, c=42) == (1, 3, (42,), {})
+ assert bar(1, 3, 42) == (1, 3, (42,), {})
+ assert bar(1, 3, c=42) == (42, 1, (3,), {})
+
+
+def test_return_multiple_args():
+ @validate(
+ Parameter(name='c'),
+ Parameter(name='a'),
+ Parameter(name='b'),
+ return_as=ReturnAs.KWARGS_WITH_NONE,
+ )
+ def bar(*args, **kwargs):
+ return args, kwargs
+
+ assert bar(a=1, b=3, c=42) == ((), {'a': 1, 'b': 3, 'c': 42})
+ assert bar(1, 3, 42) == ((), {'a': 3, 'b': 42, 'c': 1})
+ assert bar(1, 3, c=42) == ((), {'a': 1, 'b': 3, 'c': 42})
+
+
+def test_none_is_not_validated_if_not_required_kwargs_with_none():
+ @validate(Parameter(name='a', validators=[Email()], required=False), return_as=ReturnAs.KWARGS_WITH_NONE)
+ def bar(a: Optional[str]):
+ return a
+
+ assert bar(a=None) is None
+ assert bar(None) is None
+
+ with pytest.raises(expected_exception=ParameterException):
+ bar('no_email')
+
+
+def test_none_is_not_validated_if_not_required_kwargs_without_none():
+ @validate(Parameter(name='a', validators=[Email()], required=False), return_as=ReturnAs.KWARGS_WITHOUT_NONE)
+ def bar(a: Optional[str] = None):
+ return a
+
+ assert bar(a=None) is None
+ assert bar(None) is None
+
+ with pytest.raises(expected_exception=ParameterException):
+ bar('no_email')
+
+
+def test_allow_renaming_of_parameter_of_custom_validator():
+ class MyCustomValidator(Validator):
+ def validate(self, i_renamed_this_arg: Any) -> Any:
+ return i_renamed_this_arg
+
+ @validate(Parameter(name='a', validators=[MyCustomValidator()]))
+ def bar(a: int):
+ return a
+
+ assert bar(42) == 42
+ assert bar(a=42) == 42
+
+
+def test_none_is_removed_for_not_required_parameter():
+ @validate(Parameter(name='a', required=False))
+ def bar(a: int = 42):
+ return a
+
+ assert bar() == 42
+ assert bar(a=2) == 2
+ assert bar(2) == 2
+
+
+def test_default_value_is_not_validated_internal_parameter():
+ t = datetime(year=2021, month=11, day=24)
+ unix_timestamp = (t - datetime(year=1970, month=1, day=1)).total_seconds()
+
+ @validate(Parameter(name='a', required=False, default=t, validators=[DateTimeUnixTimestamp()]))
+ def bar(a: datetime) -> datetime:
+ return a
+
+ assert bar(a=unix_timestamp) == t
+ assert bar() == t
+
+
+def test_no_default_value():
+ @validate(Parameter(name='a', required=False))
+ def bar(a: datetime) -> datetime:
+ return a
+
+ with pytest.raises(expected_exception=ValidateException):
+ bar()
+
+
+def test_default_value_is_not_validated_external_parameter():
+ t = datetime(year=2021, month=11, day=24)
+
+ if 'a' in os.environ:
+ del os.environ['a']
+
+ @validate(EnvironmentVariableParameter(name='a', default=t, validators=[DateTimeUnixTimestamp()], required=False))
+ def bar(a: datetime) -> datetime:
+ return a
+
+ assert bar() == t
+
+
+@pytest.mark.asyncio
+async def test_async_instance_method():
+ class Foo:
+ @validate(Parameter(name='k', value_type=int, validators=[Min(42)]),
+ return_as=ReturnAs.KWARGS_WITHOUT_NONE)
+ async def bar(self, k):
+ return k
+
+ @validate(Parameter(name='k', value_type=int, validators=[Min(42)]), return_as=ReturnAs.ARGS)
+ async def bar_2(self, k):
+ return k
+
+ f = Foo()
+ res = await f.bar(k=42)
+ assert res == 42
+
+ with pytest.raises(expected_exception=ParameterException):
+ await f.bar(k=41)
+
+ res = await f.bar_2(k=42)
+ assert res == 42
+
+ with pytest.raises(expected_exception=ParameterException):
+ await f.bar_2(k=41)
diff --git a/tests/decorators/validate/test_validator_composite.py b/tests/decorators/validate/test_validator_composite.py
new file mode 100644
index 00000000..b80c5dae
--- /dev/null
+++ b/tests/decorators/validate/test_validator_composite.py
@@ -0,0 +1,22 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import Composite, Max, Min
+
+
+def test_validator_composite():
+ @validate(Parameter(name='x', validators=[Composite([Min(3), Max(5)])]))
+ def foo(x):
+ return x
+
+ assert foo(3) == 3
+ assert foo(4) == 4
+ assert foo(5) == 5
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(5.0001)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(2.9999)
diff --git a/tests/decorators/validate/test_validator_datetime_unix_timestamp.py b/tests/decorators/validate/test_validator_datetime_unix_timestamp.py
new file mode 100644
index 00000000..eac55138
--- /dev/null
+++ b/tests/decorators/validate/test_validator_datetime_unix_timestamp.py
@@ -0,0 +1,31 @@
+from datetime import datetime
+
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import DateTimeUnixTimestamp
+
+
+def test_validator_datetime_unix_timestamp():
+ @validate(Parameter(name='x', validators=[DateTimeUnixTimestamp()]))
+ def foo(x):
+ return x
+
+ now = datetime.now()
+ unix_timestamp = (now - datetime(year=1970, month=1, day=1)).total_seconds()
+ assert foo(unix_timestamp) == now
+ assert foo(str(unix_timestamp)) == now
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo('12.12.2020')
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo('invalid')
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo({'a': 1})
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(unix_timestamp * 1000)
diff --git a/tests/decorators/validate/test_validator_email.py b/tests/decorators/validate/test_validator_email.py
new file mode 100644
index 00000000..3d68ba2d
--- /dev/null
+++ b/tests/decorators/validate/test_validator_email.py
@@ -0,0 +1,28 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import Email
+
+
+def test_validator_email():
+ @validate(Parameter(name='x', validators=[Email()]))
+ def foo(x):
+ return x
+
+ for value in ['fred@web.de', 'genial@gmail.com', 'test@test.co.uk']:
+ assert foo(value) == value
+
+ for value in ['fred', 'fred@web', 'fred@w@eb.de', 'fred@@web.de', 'invalid@invalid']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
+
+
+def test_validator_email_converts_to_lower_case():
+ @validate(Parameter(name='x', validators=[Email(post_processor=lambda x: x.lower())]))
+ def foo(x):
+ return x
+
+ for value in ['Fred@Web.de', 'GENIAL@GMAIL.com', 'test@test.CO.UK']:
+ assert foo(value) == value.lower()
diff --git a/tests/decorators/validate/test_validator_for_each.py b/tests/decorators/validate/test_validator_for_each.py
new file mode 100644
index 00000000..082fd6a3
--- /dev/null
+++ b/tests/decorators/validate/test_validator_for_each.py
@@ -0,0 +1,30 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import ForEach, Min, Max
+
+
+def test_validator_for_each_single_child():
+ @validate(Parameter(name='x', validators=[ForEach(Min(3))]))
+ def foo(x):
+ return x
+
+ assert foo([3, 4, 5]) == [3, 4, 5]
+
+ for value in [42, [3, 2, 5]]:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
+
+
+def test_validator_for_each_multiple_children():
+ @validate(Parameter(name='x', validators=[ForEach([Min(3), Max(4)])]))
+ def foo(x):
+ return x
+
+ assert foo([3, 4]) == [3, 4]
+
+ for value in [42, [3, 2, 5]]:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
diff --git a/tests/decorators/validate/test_validator_is_enum.py b/tests/decorators/validate/test_validator_is_enum.py
new file mode 100644
index 00000000..646f5843
--- /dev/null
+++ b/tests/decorators/validate/test_validator_is_enum.py
@@ -0,0 +1,78 @@
+from enum import Enum, IntEnum
+
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import IsEnum
+
+
+class MyEnum(Enum):
+ RED = 'RED'
+ BLUE = 'BLUE'
+
+
+class MyIntEnum(IntEnum):
+ RED = 1
+ BLUE = 2
+
+
+def test_validator_is_enum_convert_true():
+ @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=True)]))
+ def foo(x):
+ return x
+
+ assert foo('RED') == MyEnum.RED
+ assert foo('BLUE') == MyEnum.BLUE
+
+ for value in ['fred', 1, 'GREEN']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
+
+
+def test_validator_is_enum_int_enum_convert_true():
+ @validate(Parameter(name='x', validators=[IsEnum(MyIntEnum, convert=True)]))
+ def foo(x):
+ return x
+
+ assert foo('1') == MyIntEnum.RED
+ assert foo('2') == MyIntEnum.BLUE
+ assert foo(1) == MyIntEnum.RED
+ assert foo(2) == MyIntEnum.BLUE
+
+ for value in ['fred', 3, 'GREEN']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
+
+
+def test_validator_is_enum_convert_false():
+ @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False)]))
+ def foo(x):
+ return x
+
+ assert foo('RED') == 'RED'
+ assert foo('BLUE') == 'BLUE'
+
+ for value in ['fred', 1, 'GREEN']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
+
+
+def test_validator_is_enum_to_upper_case():
+ @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False)]))
+ def foo(x):
+ return x
+
+ assert foo('red') == 'RED'
+ assert foo('blue') == 'BLUE'
+ assert foo('bLUe') == 'BLUE'
+
+
+def test_validator_is_enum_to_upper_case_disabled():
+ @validate(Parameter(name='x', validators=[IsEnum(MyEnum, convert=False, to_upper_case=False)]))
+ def foo(x): print(x)
+
+ for value in ['red', 'blue', 'Red', 'bLUe']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
diff --git a/tests/decorators/validate/test_validator_is_uuid.py b/tests/decorators/validate/test_validator_is_uuid.py
new file mode 100644
index 00000000..fe865c4f
--- /dev/null
+++ b/tests/decorators/validate/test_validator_is_uuid.py
@@ -0,0 +1,35 @@
+from uuid import uuid1, uuid3, uuid4, uuid5
+
+import pytest
+
+from pedantic import ForEach
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import IsUuid
+
+
+def test_validator_is_uuid():
+ @validate(Parameter(name='x', validators=[IsUuid()], required=False))
+ def foo(x):
+ return x
+
+ for id_ in [str(uuid1()), str(uuid3(uuid1(), 'b')), str(uuid4()), str(uuid5(uuid1(), 'b'))]:
+ assert foo(id_) == id_
+
+ for no_id in ['invalid', 12]:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(no_id)
+
+
+def test_validator_is_uuid_with_for_each_and_none_value():
+ @validate(Parameter(name='x', validators=[ForEach(IsUuid())]))
+ def foo(x):
+ return x
+
+ uuid = str(uuid1())
+ assert foo([]) == []
+ assert foo([uuid]) == [uuid]
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo([None])
diff --git a/tests/decorators/validate/test_validator_match_pattern.py b/tests/decorators/validate/test_validator_match_pattern.py
new file mode 100644
index 00000000..99a79c73
--- /dev/null
+++ b/tests/decorators/validate/test_validator_match_pattern.py
@@ -0,0 +1,21 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import MatchPattern
+
+
+def test_validator_match_pattern():
+ pattern = r'^(([01]\d|2[0-3]):([0-5]\d)|24:00)$'
+
+ @validate(Parameter(name='x', validators=[MatchPattern(pattern)]))
+ def foo(x):
+ return x
+
+ for value in ['00:00', '02:45', '14:59', '23:59', '24:00']:
+ assert foo(value) == value
+
+ for value in ['00:70', '24:01', '30:00', 'invalid']:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
diff --git a/tests/decorators/validate/test_validator_max.py b/tests/decorators/validate/test_validator_max.py
new file mode 100644
index 00000000..82cad490
--- /dev/null
+++ b/tests/decorators/validate/test_validator_max.py
@@ -0,0 +1,35 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators.max import Max
+
+
+def test_validator_max_length_include_boundary_true():
+ @validate(Parameter(name='x', validators=[Max(3, include_boundary=True)]))
+ def foo(x):
+ return x
+
+ assert foo(3) == 3
+ assert foo(2) == 2
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(4)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(3.001)
+
+def test_validator_max_length_include_boundary_false():
+ @validate(Parameter(name='x', validators=[Max(3, include_boundary=False)]))
+ def foo(x):
+ return x
+
+ assert foo(2.9999) == 2.9999
+ assert foo(2) == 2
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(4)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(3)
diff --git a/tests/decorators/validate/test_validator_max_length.py b/tests/decorators/validate/test_validator_max_length.py
new file mode 100644
index 00000000..bd7f2a4d
--- /dev/null
+++ b/tests/decorators/validate/test_validator_max_length.py
@@ -0,0 +1,31 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import MaxLength
+
+
+def test_validator_max_length():
+ @validate(Parameter(name='x', validators=[MaxLength(3)]))
+ def foo(x):
+ return x
+
+ assert foo('hi') == 'hi'
+ assert foo('hi!') == 'hi!'
+ assert foo([1, 2, 3]) == [1, 2, 3]
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo('hi!!')
+
+ assert err.value.message == 'hi!! is too long with length 4.'
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo([1, 2, 3, 4])
+
+ assert err.value.message == '[1, 2, 3, 4] is too long with length 4.'
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo(42)
+
+ assert err.value.message == '42 has no length.'
diff --git a/tests/decorators/validate/test_validator_min.py b/tests/decorators/validate/test_validator_min.py
new file mode 100644
index 00000000..056692cd
--- /dev/null
+++ b/tests/decorators/validate/test_validator_min.py
@@ -0,0 +1,36 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import Min
+
+
+def test_validator_min_length_include_boundary_true():
+ @validate(Parameter(name='x', validators=[Min(3, include_boundary=True)]))
+ def foo(x):
+ return x
+
+ assert foo(3) == 3
+ assert foo(4) == 4
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(2)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(2.9999)
+
+
+def test_validator_min_length_include_boundary_false():
+ @validate(Parameter(name='x', validators=[Min(3, include_boundary=False)]))
+ def foo(x):
+ return x
+
+ assert foo(3.0001) == 3.0001
+ assert foo(4) == 4
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(2)
+
+ with pytest.raises(expected_exception=ParameterException):
+ foo(3)
diff --git a/tests/decorators/validate/test_validator_min_length.py b/tests/decorators/validate/test_validator_min_length.py
new file mode 100644
index 00000000..7c9eac79
--- /dev/null
+++ b/tests/decorators/validate/test_validator_min_length.py
@@ -0,0 +1,31 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import MinLength
+
+
+def test_validator_min_length():
+ @validate(Parameter(name='x', validators=[MinLength(3)]))
+ def foo(x):
+ return x
+
+ assert foo('hi!') == 'hi!'
+ assert foo('hello') == 'hello'
+ assert foo([1, 2, 3]) == [1, 2, 3]
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo('hi')
+
+ assert err.value.message == 'hi is too short with length 2.'
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo([1, 2])
+
+ assert err.value.message == '[1, 2] is too short with length 2.'
+
+ with pytest.raises(expected_exception=ParameterException) as err:
+ foo(42)
+
+ assert err.value.message == '42 has no length.'
diff --git a/tests/decorators/validate/test_validator_not_empty.py b/tests/decorators/validate/test_validator_not_empty.py
new file mode 100644
index 00000000..2f5b8c0a
--- /dev/null
+++ b/tests/decorators/validate/test_validator_not_empty.py
@@ -0,0 +1,20 @@
+import pytest
+
+from pedantic.decorators.fn_deco_validate.exceptions import ParameterException
+from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate
+from pedantic.decorators.fn_deco_validate.parameters import Parameter
+from pedantic.decorators.fn_deco_validate.validators import NotEmpty
+
+
+def test_validator_not_empty():
+ @validate(Parameter(name='x', validators=[NotEmpty()]))
+ def foo(x):
+ return x
+
+ assert foo('hi') == 'hi'
+ assert foo(' hi ') == 'hi'
+ assert foo([1]) == [1]
+
+ for value in ['', ' ', [], {}, set()]:
+ with pytest.raises(expected_exception=ParameterException):
+ foo(value)
diff --git a/tests/mixins/__init__.py b/tests/mixins/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/mixins/test_generic_mixin.py b/tests/mixins/test_generic_mixin.py
new file mode 100644
index 00000000..b1c44f2f
--- /dev/null
+++ b/tests/mixins/test_generic_mixin.py
@@ -0,0 +1,170 @@
+from typing import TypeVar, Generic, List, Type
+
+import pytest
+
+from pedantic import GenericMixin
+
+A = TypeVar('A')
+E = TypeVar('E')
+S = TypeVar('S')
+T = TypeVar('T')
+U = TypeVar('U')
+
+
+def test_single_type_var():
+ class Foo(Generic[T], GenericMixin):
+ value: T
+
+ foo = Foo[str]()
+ assert foo.type_var == str
+ assert foo.type_vars == {T: str}
+
+ invalid = Foo()
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ invalid.type_var
+
+ assert f'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.value.args[0]
+
+
+def test_multiple_type_vars():
+ class Foo(Generic[T, U], GenericMixin):
+ value: T
+ values: List[U]
+
+ foo = Foo[str, int]()
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ foo.type_var
+
+ assert 'You have multiple type parameters. Please use "type_vars" instead of "type_var".' in err.value.args[0]
+
+ assert foo.type_vars == {T: str, U: int}
+
+ invalid = Foo()
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ invalid.type_var
+
+ assert 'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.value.args[0]
+
+
+def test_non_generic_class():
+ class Foo(GenericMixin):
+ value: int
+
+ invalid = Foo()
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ invalid.type_var
+
+ assert err.value.args[0] == 'Foo is not a generic class. To make it generic, declare it like: class Foo(Generic[T], GenericMixin):...'
+
+
+def test_call_type_var_in_constructor():
+ class Foo(Generic[T], GenericMixin):
+ def __init__(self) -> None:
+ self.x = self.type_var()
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ Foo[str]()
+
+ assert 'make sure that you do not call this in the __init__() method' in err.value.args[0]
+
+
+def test_subclass_set_type_variable():
+ class Gen(Generic[T], GenericMixin):
+ def __init__(self, value: T) -> None:
+ self.value = value
+
+ def get_type(self) -> dict[TypeVar, Type]:
+ return self.type_vars
+
+ class MyClass(Gen[int]):
+ pass
+
+ bar = Gen[int](value=4)
+ assert bar.get_type() == {T: int}
+
+ foo = MyClass(value=4)
+ assert foo.get_type() == {T: int}
+
+
+def test_subclass_with_multiple_parents():
+ class Gen(Generic[T], GenericMixin):
+ def __init__(self, value: T) -> None:
+ self.value = value
+
+ def get_type(self) -> dict[TypeVar, Type]:
+ return self.type_vars
+
+ class MyMixin:
+ value = 42
+
+ class MyClass(MyMixin, Gen[int]):
+ pass
+
+ bar = Gen[int](value=4)
+ assert bar.get_type() == {T: int}
+
+ foo = MyClass(value=4)
+ assert foo.get_type() == {T: int}
+
+
+def test_resolved_type_var_inheritance():
+ class Foo(Generic[T]): ...
+
+ class Bar(Foo[int], Generic[U], GenericMixin): ...
+
+ bar = Bar[str]()
+ assert bar.type_vars == {T: int, U: str}
+
+def test_resolved_type_var_inheritance_2():
+ class Foo(Generic[T], GenericMixin): ...
+
+ class Bar(Foo[int], Generic[U]): ...
+
+ bar = Bar[str]()
+ assert bar.type_vars == {T: int, U: str}
+
+def test_very_complex_inheritance():
+ class Foo(Generic[E], GenericMixin): ...
+ class Bar(Foo[int], Generic[S]): ...
+ class Baz(Foo[int]): ...
+ class Deep(Baz): ...
+ class Deeper(Baz, Generic[T]): ...
+
+ foo = Foo[str]()
+ actual = foo.type_vars
+ assert actual == {E: str}
+
+ bar = Bar[str]()
+ actual = bar.type_vars
+ assert actual == {E: int, S: str}
+
+ baz = Baz()
+ actual = baz.type_vars
+ assert actual == {E: int}
+
+ deep = Deep()
+ actual = deep.type_vars
+ assert actual == {E: int}
+
+ deeper = Deeper[bool]()
+ actual = deeper.type_vars
+ assert actual == {E: int, T: bool}
+
+ with pytest.raises(expected_exception=AssertionError) as err:
+ Foo().type_vars
+
+ assert 'You need to instantiate this class with type parameters! Example: Foo[int]()' in err.value.args[0]
+
+
+def test_substitution_lookup_hits():
+ class Base(Generic[A], GenericMixin): ...
+ class Mid(Base[A], Generic[A]): ...
+ class Final(Mid[int]): ...
+
+ obj = Final()
+ actual = obj.type_vars
+ assert actual == {A: int}
diff --git a/tests/mixins/test_with_decorated_methods.py b/tests/mixins/test_with_decorated_methods.py
new file mode 100644
index 00000000..27af82ba
--- /dev/null
+++ b/tests/mixins/test_with_decorated_methods.py
@@ -0,0 +1,127 @@
+from functools import wraps
+from typing import TypeVar, Generic, NoReturn
+
+from pedantic import DecoratorType, create_decorator, WithDecoratedMethods
+
+T = TypeVar('T')
+
+class Decorators(DecoratorType):
+ FOO = '_foo'
+ BAR = '_bar'
+
+
+foo = create_decorator(decorator_type=Decorators.FOO)
+bar = create_decorator(decorator_type=Decorators.BAR)
+
+
+def test_no_decorated_methods():
+ class MyClass(WithDecoratedMethods[Decorators]):
+ pass
+
+ instance = MyClass()
+ assert instance.get_decorated_functions() == {Decorators.FOO: {}, Decorators.BAR: {}}
+
+
+def test_class_with_bad_property():
+ class MyClass(WithDecoratedMethods[Decorators]):
+ @property
+ def bad(self) -> NoReturn:
+ raise RuntimeError('bad man')
+
+ instance = MyClass()
+ assert instance.get_decorated_functions() == {Decorators.FOO: {}, Decorators.BAR: {}}
+
+
+def test_with_decorated_methods_sync():
+ class MyClass(WithDecoratedMethods[Decorators]):
+ @foo(42)
+ def m1(self) -> None:
+ print('bar')
+
+ @foo(value=43)
+ def m2(self) -> None:
+ print('bar')
+
+ @bar(value=44)
+ def m3(self) -> None:
+ print('bar')
+
+ instance = MyClass()
+ expected = {
+ Decorators.FOO: {
+ instance.m1: 42,
+ instance.m2: 43,
+ },
+ Decorators.BAR: {
+ instance.m3: 44,
+ }
+ }
+ assert instance.get_decorated_functions() == expected
+
+
+def test_with_decorated_methods_async():
+ class MyClass(WithDecoratedMethods[Decorators]):
+ @foo(42)
+ async def m1(self) -> None:
+ print('bar')
+
+ @foo(value=43)
+ async def m2(self) -> None:
+ print('bar')
+
+ @bar(value=44)
+ async def m3(self) -> None:
+ print('bar')
+
+ instance = MyClass()
+ expected = {
+ Decorators.FOO: {
+ instance.m1: 42,
+ instance.m2: 43,
+ },
+ Decorators.BAR: {
+ instance.m3: 44,
+ }
+ }
+ assert instance.get_decorated_functions() == expected
+
+
+def test_with_custom_transformation():
+ def my_transformation(f, decorator_type, value):
+ assert decorator_type == Decorators.BAR
+ assert value == 42
+
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ f(*args, **kwargs)
+ return 4422 # we add a return value
+
+ return wrapper
+
+ my_decorator = create_decorator(decorator_type=Decorators.BAR, transformation=my_transformation)
+
+ class MyClass(WithDecoratedMethods[Decorators]):
+ @my_decorator(42)
+ def m1(self) -> int:
+ return 1
+
+ instance = MyClass()
+ expected = {
+ Decorators.BAR: {
+ instance.m1: 42,
+ },
+ Decorators.FOO: {},
+ }
+ assert instance.get_decorated_functions() == expected
+
+ assert instance.m1() == 4422 # check that transformation was applied
+
+
+def test_with_decorated_methods_can_have_generic_child_class():
+ class MyClass(Generic[T], WithDecoratedMethods[Decorators]):
+ @foo(42)
+ def m1(self) -> None: ...
+
+ instance = MyClass[int]()
+ actual = instance.get_decorated_functions()
+ assert actual == {Decorators.FOO: {instance.m1: 42}, Decorators.BAR: {}}
diff --git a/tests/models/__init__.py b/tests/models/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/models/test_decorated_function.py b/tests/models/test_decorated_function.py
new file mode 100644
index 00000000..969a50fc
--- /dev/null
+++ b/tests/models/test_decorated_function.py
@@ -0,0 +1,142 @@
+from pedantic.models.decorated_function import DecoratedFunction
+
+
+def test_static_method():
+ def f_1(): pass
+
+ deco_f = DecoratedFunction(f_1)
+ assert deco_f.is_static_method is False
+
+ class MyClass:
+ def f_1(self): pass
+
+ @staticmethod
+ def f_2(): pass
+
+ @classmethod
+ def f_3(cls): pass
+
+ deco_f_1 = DecoratedFunction(MyClass.f_1)
+ deco_f_2 = DecoratedFunction(MyClass.f_2)
+ deco_f_3 = DecoratedFunction(MyClass.f_3)
+
+ assert deco_f_1.is_static_method is False
+ assert deco_f_2.is_static_method is True
+ assert deco_f_3.is_static_method is False
+
+
+def test_function_wants_args():
+ def f_1(*args, **kwargs): pass
+
+ def f_2(a, b, *args, **kwargs): pass
+
+ def f_3(a, b, *args): pass
+
+ def f_4(*args): pass
+
+ def f_5(): pass
+
+ assert DecoratedFunction(f_1).wants_args is True
+ assert DecoratedFunction(f_2).wants_args is True
+ assert DecoratedFunction(f_3).wants_args is True
+ assert DecoratedFunction(f_4).wants_args is True
+ assert DecoratedFunction(f_5).wants_args is False
+
+ class MyClass:
+ def f(self): pass
+
+ @staticmethod
+ def g(): pass
+
+ assert DecoratedFunction(MyClass.f).wants_args is False
+ assert DecoratedFunction(MyClass.g).wants_args is False
+
+
+def test_is_property_setter():
+ def f_1(): pass
+
+ assert DecoratedFunction(f_1).is_property_setter is False
+
+ class MyClass:
+ _h = 42
+
+ def f_1(self): pass
+
+ @staticmethod
+ def f_2(): pass
+
+ assert DecoratedFunction(MyClass.f_1).is_property_setter is False
+ assert DecoratedFunction(MyClass.f_2).is_property_setter is False
+
+
+def test_wants_kwargs():
+ def f_1(*args, **kwargs): pass
+
+ def f_2(a, b, *args, **kwargs): pass
+
+ def f_3(a, b, *args): pass
+
+ def f_4(*args): pass
+
+ def f_5(): pass
+
+ def f_6(a, b, c): pass
+
+ assert DecoratedFunction(f_1).should_have_kwargs is False
+ assert DecoratedFunction(f_2).should_have_kwargs is False
+ assert DecoratedFunction(f_3).should_have_kwargs is False
+ assert DecoratedFunction(f_4).should_have_kwargs is False
+ assert DecoratedFunction(f_5).should_have_kwargs is True
+ assert DecoratedFunction(f_6).should_have_kwargs is True
+
+ class A:
+ def f(self): pass
+
+ @staticmethod
+ def g(): pass
+
+ def __compare__(self, other): pass
+
+ assert DecoratedFunction(A.f).should_have_kwargs is True
+ assert DecoratedFunction(A.g).should_have_kwargs is True
+ assert DecoratedFunction(A.__compare__).should_have_kwargs is False
+
+
+def test_instance_method():
+ def h(): pass
+
+ assert DecoratedFunction(h).is_instance_method is False
+
+ class A:
+ def f(self): pass
+
+ @staticmethod
+ def g(): pass
+
+ assert DecoratedFunction(A.f).is_instance_method is True
+ assert DecoratedFunction(A.g).is_instance_method is False
+
+
+def test_num_decorators():
+ def decorator(f):
+ return f
+
+ def f_1(): pass
+
+ @decorator
+ def f_2(): pass
+
+ @decorator
+ @decorator
+ def f_3(): pass
+
+ @decorator
+ @decorator
+ @decorator
+ def f_4():
+ pass
+
+ assert DecoratedFunction(f_1).num_of_decorators == 0
+ assert DecoratedFunction(f_2).num_of_decorators == 1
+ assert DecoratedFunction(f_3).num_of_decorators == 2
+ assert DecoratedFunction(f_4).num_of_decorators == 3
diff --git a/tests/models/test_generator_wrapper.py b/tests/models/test_generator_wrapper.py
new file mode 100644
index 00000000..76ec6774
--- /dev/null
+++ b/tests/models/test_generator_wrapper.py
@@ -0,0 +1,33 @@
+from typing import Iterator
+
+import pytest
+
+from pedantic.models import GeneratorWrapper
+
+
+def test_generator_wrapper():
+ def gen_func() -> Iterator[int]:
+ num = 0
+
+ while num < 100:
+ yield num
+ num += 1
+
+ generator = gen_func()
+
+ g = GeneratorWrapper(
+ wrapped=generator,
+ expected_type=Iterator[int],
+ err_msg='msg',
+ type_vars={},
+ )
+
+ print(sum([x for x in g]))
+
+ with pytest.raises(expected_exception=Exception):
+ g.throw(Exception('error'))
+
+ with pytest.raises(expected_exception=AttributeError):
+ g.invalid
+
+ g.close()
diff --git a/tests/test_environment_variables.py b/tests/test_environment_variables.py
new file mode 100644
index 00000000..5166427c
--- /dev/null
+++ b/tests/test_environment_variables.py
@@ -0,0 +1,35 @@
+import pytest
+
+from pedantic.exceptions import PedanticTypeCheckException
+from pedantic.env_var_logic import enable_pedantic, disable_pedantic, is_enabled
+from pedantic.decorators.fn_deco_pedantic import pedantic
+
+
+def test_pedantic_enabled():
+ enable_pedantic()
+
+ @pedantic
+ def some_method():
+ return 42
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ some_method()
+
+
+def test_pedantic_disabled():
+ disable_pedantic()
+
+ @pedantic
+ def some_method():
+ return 42
+
+ some_method()
+
+
+def test_enable_disable():
+ enable_pedantic()
+ assert is_enabled() is True
+ disable_pedantic()
+ assert is_enabled() is False
+ enable_pedantic()
+ assert is_enabled() is True
diff --git a/tests/test_the_tests.py b/tests/test_the_tests.py
new file mode 100644
index 00000000..2335f578
--- /dev/null
+++ b/tests/test_the_tests.py
@@ -0,0 +1,19 @@
+from pathlib import Path
+
+def test_all_test_files_follow_pytest_naming_convention():
+ tests_dir = Path(__file__).parent
+ invalid_files = []
+
+ for path in tests_dir.rglob("*.py"):
+ name = path.name
+
+ if name in {"__init__.py", "conftest.py"}:
+ continue
+
+ if not name.startswith("test_"):
+ invalid_files.append(path.relative_to(tests_dir))
+
+ assert not invalid_files, (
+ "Found Python files in tests/ that do not follow pytest naming conventions:\n"
+ + "\n".join(str(p) for p in invalid_files)
+ )
diff --git a/tests/type_checking_logic/__init__.py b/tests/type_checking_logic/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/type_checking_logic/test_assert_value_matches_type.py b/tests/type_checking_logic/test_assert_value_matches_type.py
new file mode 100644
index 00000000..30240ff9
--- /dev/null
+++ b/tests/type_checking_logic/test_assert_value_matches_type.py
@@ -0,0 +1,216 @@
+from abc import ABC
+from dataclasses import dataclass
+from datetime import datetime
+from typing import Callable, Awaitable, Coroutine, Any, Union, Optional, Generic, TypeVar, Tuple
+
+import pytest
+
+from pedantic.exceptions import PedanticTypeCheckException
+from pedantic.type_checking_logic.check_types import assert_value_matches_type
+
+
+@dataclass
+class Foo:
+ value: int
+
+
+def test_callable():
+ def _cb(foo: Foo) -> str:
+ return str(foo.value)
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., str],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., int],
+ err='',
+ type_vars={},
+ )
+
+def test_callable_return_type_none():
+ def _cb(foo: Foo) -> None:
+ return print(foo)
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., None],
+ err='',
+ type_vars={},
+ )
+
+def test_callable_awaitable():
+ async def _cb(foo: Foo) -> str:
+ return str(foo.value)
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[str]],
+ err='',
+ type_vars={},
+ )
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[Any]],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[int]],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., str],
+ err='',
+ type_vars={},
+ )
+
+
+def test_callable_coroutine():
+ async def _cb(foo: Foo) -> str:
+ return str(foo.value)
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Coroutine[None, None, str]],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Coroutine[None, None, int]],
+ err='',
+ type_vars={},
+ )
+
+
+def test_callable_awaitable_with_none_return_type():
+ async def _cb(foo: Foo) -> None:
+ print(foo)
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[None]],
+ err='',
+ type_vars={},
+ )
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[Any]],
+ err='',
+ type_vars={},
+ )
+
+
+def test_callable_with_old_union_type_hint():
+ async def _cb(_: str) -> Union[int, None]:
+ return 42
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[Union[int, None]]],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[int]],
+ err='',
+ type_vars={},
+ )
+
+
+def test_callable_with_new_union_type_hint():
+ async def _cb(_: str) -> int | None:
+ return 42
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[int | None]],
+ err='',
+ type_vars={},
+ )
+
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[Any]],
+ err='',
+ type_vars={},
+ )
+
+ with pytest.raises(expected_exception=PedanticTypeCheckException):
+ assert_value_matches_type(
+ value=_cb,
+ type_=Callable[..., Awaitable[int]],
+ err='',
+ type_vars={},
+ )
+
+
+def test_forward_ref_inheritance():
+ T = TypeVar('T')
+
+ class State(Generic[T], ABC):
+ pass
+
+ class StateMachine(Generic[T], ABC):
+ pass
+
+ class MachineState(State['MachineStateMachine']):
+ pass
+
+ class OfflineMachineState(MachineState):
+ pass
+
+ class MachineStateMachine(StateMachine[MachineState]):
+ pass
+
+ assert_value_matches_type(
+ value=OfflineMachineState(),
+ type_=Optional['MachineState'],
+ err='',
+ type_vars={},
+ context=locals(),
+ )
+
+
+def test_tuple_with_ellipsis():
+ """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/75 """
+
+ assert_value_matches_type(
+ value=(1, 2.0, 'hello'),
+ type_=Tuple[Any, ...],
+ err='',
+ type_vars={},
+ context=locals(),
+ )
+
+
+def test_union_of_callable():
+ """ Regression test for https://github.com/LostInDarkMath/pedantic-python-decorators/issues/74 """
+
+ assert_value_matches_type(
+ value=datetime.now(),
+ type_=Union[datetime, Callable[[], datetime]],
+ err='',
+ type_vars={},
+ context=locals(),
+ )
diff --git a/tests/type_checking_logic/test_resolve_forward_ref.py b/tests/type_checking_logic/test_resolve_forward_ref.py
new file mode 100644
index 00000000..003a8879
--- /dev/null
+++ b/tests/type_checking_logic/test_resolve_forward_ref.py
@@ -0,0 +1,28 @@
+from typing import List, Optional, Union
+
+import pytest
+
+from pedantic.type_checking_logic.resolve_forward_ref import resolve_forward_ref
+
+
+def test_resolve_forward_ref_primitive_types():
+ assert resolve_forward_ref(type_='int') == int
+ assert resolve_forward_ref(type_='float') == float
+ assert resolve_forward_ref(type_='str') == str
+ assert resolve_forward_ref(type_='bool') == bool
+
+def test_resolve_forward_ref_typing_types():
+ assert resolve_forward_ref(type_='List[int]') == List[int]
+ assert resolve_forward_ref(type_='Optional[List[Union[str, float]]]') == Optional[List[Union[str, float]]]
+
+def test_unresolvable_type():
+ with pytest.raises(NameError):
+ resolve_forward_ref(type_='Invalid')
+
+def test_resolve_forward_ref_custom_class():
+ class Foo:
+ pass
+
+ context = locals()
+ assert resolve_forward_ref(type_='Foo', context=context) == Foo
+ assert resolve_forward_ref(type_='Optional[Foo]', context=context) == Optional[Foo]
diff --git a/type_hint_parser_dependency_graph.graphml b/type_hint_parser_dependency_graph.graphml
deleted file mode 100644
index 81202670..00000000
--- a/type_hint_parser_dependency_graph.graphml
+++ /dev/null
@@ -1,373 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- _is_instance
-
-
-
-
-
-
-
-
-
-
- _get_name
-
-
-
-
-
-
-
-
-
-
- _is_generic
-
-
-
-
-
-
-
-
-
-
- _is_subtype
-
-
-
-
-
-
-
-
-
-
- _instancecheck_callable
-
-
-
-
-
-
-
-
-
-
- _has_required_type_arguments
-
-
-
-
-
-
-
-
-
-
- _get_type_arguments
-
-
-
-
-
-
-
-
-
-
- _get_class_of_type_annotation
-
-
-
-
-
-
-
-
-
-
- _instancecheck_union
-
-
-
-
-
-
-
-
-
-
- _instancecheck_iterable
-
-
-
-
-
-
-
-
-
-
- _instancecheck_mapping
-
-
-
-
-
-
-
-
-
-
- _instancecheck_itemsview
-
-
-
-
-
-
-
-
-
-
- _instancecheck_tuple
-
-
-
-
-
-
-
-
-
-
- _is_type_newtype
-
-
-
-
-
-
-
-
-
-
- _get_base_generic
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From 8f06e53dca18a3be4fdfa80c89ecbc23a03dbe56 Mon Sep 17 00:00:00 2001
From: Willi Sontopski <32729196+LostInDarkMath@users.noreply.github.com>
Date: Mon, 9 Feb 2026 20:54:04 +0100
Subject: [PATCH 2/3] add message parameter to @deprecated
---
CHANGELOG.md | 3 +-
.../decorators/fn_deco_deprecated.html | 38 +++--
docs/pedantic/index.html | 5 -
.../decorators/cls_deco_frozen_dataclass.py | 5 -
pedantic/decorators/fn_deco_count_calls.py | 5 -
pedantic/decorators/fn_deco_deprecated.py | 37 +++--
.../fn_deco_does_same_as_function.py | 5 -
pedantic/decorators/fn_deco_in_subprocess.py | 6 -
pedantic/decorators/fn_deco_mock.py | 5 -
pedantic/decorators/fn_deco_overrides.py | 5 -
pedantic/decorators/fn_deco_pedantic.py | 5 -
pedantic/decorators/fn_deco_rename_kwargs.py | 5 -
pedantic/decorators/fn_deco_require_kwargs.py | 5 -
pedantic/decorators/fn_deco_timer.py | 5 -
pedantic/decorators/fn_deco_trace.py | 5 -
.../decorators/fn_deco_trace_if_returns.py | 5 -
pedantic/decorators/fn_deco_unimplemented.py | 5 -
pedantic/helper_methods.py | 8 --
pedantic/mixins/generic_mixin.py | 5 -
.../check_generic_classes.py | 5 -
pedantic/type_checking_logic/check_types.py | 5 -
tests/decorators/test_deprecated.py | 30 ++++
tests/decorators/test_overrides.py | 97 +++++++++++++
.../test_small_method_decorators.py | 131 +-----------------
24 files changed, 182 insertions(+), 248 deletions(-)
delete mode 100644 pedantic/helper_methods.py
create mode 100644 tests/decorators/test_deprecated.py
create mode 100644 tests/decorators/test_overrides.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89631c75..0851406c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
# Changelog
## Pedantic 2.4.0
- migrate from unittest to pytest
-- exclude tests from package
+- exclude tests from package deployment
+- add an optional `message` argument to `@deprecated` decorator
## Pedantic 2.3.3
- [fixed validation error message in MinLength validator](https://github.com/LostInDarkMath/pedantic-python-decorators/issues/111)
diff --git a/docs/pedantic/decorators/fn_deco_deprecated.html b/docs/pedantic/decorators/fn_deco_deprecated.html
index 7e3dab10..5dd14fcd 100644
--- a/docs/pedantic/decorators/fn_deco_deprecated.html
+++ b/docs/pedantic/decorators/fn_deco_deprecated.html
@@ -45,37 +45,53 @@ Module pedantic.decorators.fn_deco_deprecated
Functions
-def deprecated(func: Callable[..., ~ReturnType]) ‑> Callable[..., ~ReturnType]
+def deprecated(func: Callable[..., ~ReturnType] | None = None, message: str = '') ‑> Callable[..., ~ReturnType] | Callable[[Callable[..., ~ReturnType]], Callable[..., ~ReturnType]]
-
Expand source code
-def deprecated(func: F) -> F:
+def deprecated(func: F | None = None, message: str = '') -> F | Callable[[F], F]:
"""
Use this decorator to mark a function as deprecated. It will raise a warning when the function is called.
+ You can specify an optional reason or message to display with the warning.
Example:
-
>>> @deprecated
... def my_function(a, b, c):
... pass
- >>> my_function(5, 4, 3)
+ >>> my_function(5, 4, 3) # doctest: +SKIP
+ >>> @deprecated(message='Will be removed soon. Please use my_function_new_instead.')
+ ... def my_function(a, b, c):
+ ... pass
+ >>> my_function(5, 4, 3) # doctest: +SKIP
"""
- @wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
- _raise_warning(msg=f'Call to deprecated function {func.__qualname__}.', category=DeprecationWarning)
- return func(*args, **kwargs)
- return wrapper
+ def decorator(fun: F) -> F:
+ @wraps(fun)
+ def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
+ msg = f'Call to deprecated function {fun.__qualname__}.'
+
+ if message:
+ msg += f'\nReason: {message}'
+
+ warnings.warn(message=msg, category=DeprecationWarning, stacklevel=2)
+ return fun(*args, **kwargs)
+ return wrapper
+ return decorator if func is None else decorator(func)
-Use this decorator to mark a function as deprecated. It will raise a warning when the function is called.
+Use this decorator to mark a function as deprecated. It will raise a warning when the function is called.
+You can specify an optional reason or message to display with the warning.
Example:
>>> @deprecated
... def my_function(a, b, c):
... pass
->>> my_function(5, 4, 3)
+>>> my_function(5, 4, 3) # doctest: +SKIP
+>>> @deprecated(message='Will be removed soon. Please use my_function_new_instead.')
+... def my_function(a, b, c):
+... pass
+>>> my_function(5, 4, 3) # doctest: +SKIP
diff --git a/docs/pedantic/index.html b/docs/pedantic/index.html
index da96160b..c2b1778b 100644
--- a/docs/pedantic/index.html
+++ b/docs/pedantic/index.html
@@ -64,10 +64,6 @@ Sub-modules
-
-pedantic.helper_methods
--
-
-
pedantic.mixins
-
@@ -102,7 +98,6 @@
Sub-modules
pedantic.examples
pedantic.exceptions
pedantic.get_context
-pedantic.helper_methods
pedantic.mixins
pedantic.models
pedantic.type_checking_logic
diff --git a/pedantic/decorators/cls_deco_frozen_dataclass.py b/pedantic/decorators/cls_deco_frozen_dataclass.py
index aeff42e6..70e90354 100644
--- a/pedantic/decorators/cls_deco_frozen_dataclass.py
+++ b/pedantic/decorators/cls_deco_frozen_dataclass.py
@@ -142,8 +142,3 @@ def validate_types(self, *, _context: Dict[str, Type] = None) -> None:
return decorator
return decorator(cls_=cls)
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_count_calls.py b/pedantic/decorators/fn_deco_count_calls.py
index 7fed2abb..3fc1ebd0 100644
--- a/pedantic/decorators/fn_deco_count_calls.py
+++ b/pedantic/decorators/fn_deco_count_calls.py
@@ -30,8 +30,3 @@ def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
wrapper.num_calls = 0
return wrapper
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_deprecated.py b/pedantic/decorators/fn_deco_deprecated.py
index 4a0bae9f..62772466 100644
--- a/pedantic/decorators/fn_deco_deprecated.py
+++ b/pedantic/decorators/fn_deco_deprecated.py
@@ -1,29 +1,40 @@
+import warnings
from functools import wraps
-from typing import Any
+from typing import Any, Callable, overload
from pedantic.constants import F, ReturnType
-from pedantic.helper_methods import _raise_warning
+@overload
+def deprecated(func: F) -> F: ...
-def deprecated(func: F) -> F:
+@overload
+def deprecated(*, message: str = '') -> Callable[[F], F]: ...
+
+def deprecated(func: F | None = None, message: str = '') -> F | Callable[[F], F]:
"""
Use this decorator to mark a function as deprecated. It will raise a warning when the function is called.
+ You can specify an optional reason or message to display with the warning.
Example:
-
>>> @deprecated
... def my_function(a, b, c):
... pass
- >>> my_function(5, 4, 3)
+ >>> my_function(5, 4, 3) # doctest: +SKIP
+ >>> @deprecated(message='Will be removed soon. Please use my_function_new_instead.')
+ ... def my_function(a, b, c):
+ ... pass
+ >>> my_function(5, 4, 3) # doctest: +SKIP
"""
- @wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
- _raise_warning(msg=f'Call to deprecated function {func.__qualname__}.', category=DeprecationWarning)
- return func(*args, **kwargs)
- return wrapper
+ def decorator(fun: F) -> F:
+ @wraps(fun)
+ def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
+ msg = f'Call to deprecated function {fun.__qualname__}.'
+ if message:
+ msg += f'\nReason: {message}'
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
+ warnings.warn(message=msg, category=DeprecationWarning, stacklevel=2)
+ return fun(*args, **kwargs)
+ return wrapper
+ return decorator if func is None else decorator(func)
diff --git a/pedantic/decorators/fn_deco_does_same_as_function.py b/pedantic/decorators/fn_deco_does_same_as_function.py
index 5629cabd..0ce47eaa 100644
--- a/pedantic/decorators/fn_deco_does_same_as_function.py
+++ b/pedantic/decorators/fn_deco_does_same_as_function.py
@@ -52,8 +52,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
return wrapper
return decorator
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_in_subprocess.py b/pedantic/decorators/fn_deco_in_subprocess.py
index 8809d49e..b308acb0 100644
--- a/pedantic/decorators/fn_deco_in_subprocess.py
+++ b/pedantic/decorators/fn_deco_in_subprocess.py
@@ -120,9 +120,3 @@ def _inner(tx: Connection, fun: Callable[..., Union[T, Awaitable[T]]], *a, **kw_
tx.send(SubprocessError(ex=ex))
else:
tx.send(res)
-
-
-if __name__ == '__main__':
- import doctest
-
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_mock.py b/pedantic/decorators/fn_deco_mock.py
index 47ca926d..75639183 100644
--- a/pedantic/decorators/fn_deco_mock.py
+++ b/pedantic/decorators/fn_deco_mock.py
@@ -36,8 +36,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
else:
return wrapper
return decorator
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_overrides.py b/pedantic/decorators/fn_deco_overrides.py
index ed6727b6..cdfa850f 100644
--- a/pedantic/decorators/fn_deco_overrides.py
+++ b/pedantic/decorators/fn_deco_overrides.py
@@ -29,8 +29,3 @@ def decorator(func: F) -> F:
f'Base class "{base_class.__name__}" does not have such a method "{name}".')
return func
return decorator
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_pedantic.py b/pedantic/decorators/fn_deco_pedantic.py
index 44a8ccfd..1ea79359 100644
--- a/pedantic/decorators/fn_deco_pedantic.py
+++ b/pedantic/decorators/fn_deco_pedantic.py
@@ -77,8 +77,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
def pedantic_require_docstring(func: Optional[F] = None) -> F:
"""Shortcut for @pedantic(require_docstring=True) """
return pedantic(func=func, require_docstring=True)
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_rename_kwargs.py b/pedantic/decorators/fn_deco_rename_kwargs.py
index e7a416b5..b529a22c 100644
--- a/pedantic/decorators/fn_deco_rename_kwargs.py
+++ b/pedantic/decorators/fn_deco_rename_kwargs.py
@@ -45,8 +45,3 @@ def wrapper(*args, **kwargs) -> ReturnType:
return func(*args, **result_kwargs)
return wrapper
return decorator
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_require_kwargs.py b/pedantic/decorators/fn_deco_require_kwargs.py
index 4ddc0d37..f763b31c 100644
--- a/pedantic/decorators/fn_deco_require_kwargs.py
+++ b/pedantic/decorators/fn_deco_require_kwargs.py
@@ -31,8 +31,3 @@ def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
call.assert_uses_kwargs()
return func(*args, **kwargs)
return wrapper
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_timer.py b/pedantic/decorators/fn_deco_timer.py
index c1751b08..843fc477 100644
--- a/pedantic/decorators/fn_deco_timer.py
+++ b/pedantic/decorators/fn_deco_timer.py
@@ -42,8 +42,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
return async_wrapper
else:
return wrapper
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_trace.py b/pedantic/decorators/fn_deco_trace.py
index 2c4cb498..aab72668 100644
--- a/pedantic/decorators/fn_deco_trace.py
+++ b/pedantic/decorators/fn_deco_trace.py
@@ -39,8 +39,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
return async_wrapper
else:
return wrapper
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_trace_if_returns.py b/pedantic/decorators/fn_deco_trace_if_returns.py
index c7f3954e..5ef3a693 100644
--- a/pedantic/decorators/fn_deco_trace_if_returns.py
+++ b/pedantic/decorators/fn_deco_trace_if_returns.py
@@ -46,8 +46,3 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> ReturnType:
else:
return wrapper
return decorator
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/decorators/fn_deco_unimplemented.py b/pedantic/decorators/fn_deco_unimplemented.py
index 3db7a9e0..e84e513d 100644
--- a/pedantic/decorators/fn_deco_unimplemented.py
+++ b/pedantic/decorators/fn_deco_unimplemented.py
@@ -24,8 +24,3 @@ def unimplemented(func: F) -> F:
def wrapper(*args: Any, **kwargs: Any) -> ReturnType:
raise NotImplementedException(f'Function "{func.__qualname__}" is not implemented yet!')
return wrapper
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/helper_methods.py b/pedantic/helper_methods.py
deleted file mode 100644
index 34487999..00000000
--- a/pedantic/helper_methods.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import warnings
-from typing import Type
-
-
-def _raise_warning(msg: str, category: Type[Warning]) -> None:
- warnings.simplefilter(action='always', category=category)
- warnings.warn(message=msg, category=category, stacklevel=2)
- warnings.simplefilter(action='default', category=category)
diff --git a/pedantic/mixins/generic_mixin.py b/pedantic/mixins/generic_mixin.py
index 7f542622..94ed2987 100644
--- a/pedantic/mixins/generic_mixin.py
+++ b/pedantic/mixins/generic_mixin.py
@@ -140,8 +140,3 @@ def class_name(self) -> str:
""" Get the name of the class of this instance. """
return type(self).__name__
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/type_checking_logic/check_generic_classes.py b/pedantic/type_checking_logic/check_generic_classes.py
index e6ba3c17..d0904a6b 100644
--- a/pedantic/type_checking_logic/check_generic_classes.py
+++ b/pedantic/type_checking_logic/check_generic_classes.py
@@ -136,8 +136,3 @@ def _remove_comments_and_spaces_from_src_line(line: str) -> str:
'm=MyClass[Parent](a=Child1())'
"""
return line.split('#')[0].replace(' ', '')
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/pedantic/type_checking_logic/check_types.py b/pedantic/type_checking_logic/check_types.py
index 4e2e1646..31f70540 100644
--- a/pedantic/type_checking_logic/check_types.py
+++ b/pedantic/type_checking_logic/check_types.py
@@ -1063,8 +1063,3 @@ def convert_to_typing_types(x: typing.Type) -> typing.Type:
return x # new since Python 3.14
raise RuntimeError(x)
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
diff --git a/tests/decorators/test_deprecated.py b/tests/decorators/test_deprecated.py
new file mode 100644
index 00000000..d5a4fd5c
--- /dev/null
+++ b/tests/decorators/test_deprecated.py
@@ -0,0 +1,30 @@
+import pytest
+
+from pedantic import deprecated
+
+
+def test_deprecated_no_args():
+ @deprecated
+ def old_method(i: int) -> str: return str(i)
+
+ with pytest.warns(DeprecationWarning, match="deprecated"):
+ old_method(42)
+
+
+def test_deprecated_with_args():
+ @deprecated(message="my deprecation message")
+ def old_method(i: int) -> str:
+ return str(i)
+
+ with pytest.warns(DeprecationWarning, match="my deprecation message"):
+ old_method(42)
+
+
+@pytest.mark.asyncio
+async def test_deprecated_async():
+ @deprecated
+ async def old_method(i: int) -> str:
+ return str(i)
+
+ with pytest.warns(DeprecationWarning, match="deprecated"):
+ await old_method(42)
diff --git a/tests/decorators/test_overrides.py b/tests/decorators/test_overrides.py
new file mode 100644
index 00000000..fb234870
--- /dev/null
+++ b/tests/decorators/test_overrides.py
@@ -0,0 +1,97 @@
+import asyncio
+from abc import abstractmethod, ABC
+
+import pytest
+
+from pedantic import overrides
+from pedantic.exceptions import PedanticOverrideException
+
+
+def test_overrides_parent_has_no_such_method():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ def operation(self): pass
+
+
+def test_overrides_all_good():
+ class MyClassA:
+ def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ def operation(self):
+ return 42
+
+ b = MyClassB()
+ b.operation()
+
+
+def test_overrides_static_method():
+ class MyClassA:
+ @staticmethod
+ def operation(): pass
+
+ class MyClassB(MyClassA):
+ @staticmethod
+ @overrides(MyClassA)
+ def operation():
+ return 42
+
+ b = MyClassB()
+ assert b.operation() == 42
+ assert MyClassB.operation() == 42
+
+
+def test_overrides_below_property():
+ class MyClassA(ABC):
+ @property
+ @abstractmethod
+ def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @property
+ @overrides(MyClassA) # Note: it does not work the other way around
+ def operation(self):
+ return 43
+
+ b = MyClassB()
+ assert b.operation == 43
+
+
+def test_overrides_function():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ @overrides(MyClassA)
+ def operation(): return 42
+
+
+@pytest.mark.asyncio
+async def test_overrides_async_instance_method() -> None:
+ class MyClassA:
+ async def operation(self): pass
+
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ async def operation(self):
+ await asyncio.sleep(0)
+ return 42
+
+ b = MyClassB()
+ await b.operation()
+
+
+@pytest.mark.asyncio
+async def test_overrides_parent_has_no_such_method_async():
+ class MyClassA:
+ pass
+
+ with pytest.raises(expected_exception=PedanticOverrideException):
+ class MyClassB(MyClassA):
+ @overrides(MyClassA)
+ async def operation(self): return 42
diff --git a/tests/decorators/test_small_method_decorators.py b/tests/decorators/test_small_method_decorators.py
index b0f98c55..fd810067 100644
--- a/tests/decorators/test_small_method_decorators.py
+++ b/tests/decorators/test_small_method_decorators.py
@@ -1,98 +1,11 @@
import asyncio
import warnings
-from abc import abstractmethod
import pytest
-from pedantic import overrides, timer, count_calls, trace, trace_if_returns, does_same_as_function, deprecated, \
+from pedantic import timer, count_calls, trace, trace_if_returns, does_same_as_function, deprecated, \
unimplemented, mock, require_kwargs
-from pedantic.exceptions import NotImplementedException, PedanticOverrideException, PedanticCallWithArgsException
-
-
-def test_overrides_parent_has_no_such_method():
- class MyClassA:
- pass
-
- with pytest.raises(expected_exception=PedanticOverrideException):
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- def operation(self): pass
-
-
-def test_overrides_all_good():
- class MyClassA:
- def operation(self): pass
-
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- def operation(self):
- return 42
-
- b = MyClassB()
- b.operation()
-
-
-def test_overrides_static_method():
- class MyClassA:
- @staticmethod
- def operation(): pass
-
- class MyClassB(MyClassA):
- @staticmethod
- @overrides(MyClassA)
- def operation():
- return 42
-
- b = MyClassB()
- assert b.operation() == 42
- assert MyClassB.operation() == 42
-
-
-def test_overrides_below_property():
- class MyClassA:
- @property
- @abstractmethod
- def operation(self): pass
-
- class MyClassB(MyClassA):
- @property
- @overrides(MyClassA) # Note: it does not work the other way around
- def operation(self):
- return 43
-
- b = MyClassB()
- assert b.operation == 43
-
-
-def test_overrides_function():
- class MyClassA:
- pass
-
- with pytest.raises(expected_exception=PedanticOverrideException):
- @overrides(MyClassA)
- def operation(): return 42
-
-
-def test_deprecated_1():
- @deprecated
- def old_method(i: int) -> str: return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- old_method(42)
- assert len(w) == 1
- assert issubclass(w[-1].category, DeprecationWarning)
- assert "deprecated" in str(w[-1].message)
-
-
-def test_deprecated_2():
- def old_method(i: int) -> str:
- return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- old_method(42)
- assert not len(w) == 1
+from pedantic.exceptions import NotImplementedException, PedanticCallWithArgsException
def test_unimplemented():
@@ -162,32 +75,6 @@ def other_method(x, y, z):
other_method(4, 5, 6)
-@pytest.mark.asyncio
-async def test_overrides_async_instance_method() -> None:
- class MyClassA:
- async def operation(self): pass
-
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- async def operation(self):
- await asyncio.sleep(0)
- return 42
-
- b = MyClassB()
- await b.operation()
-
-
-@pytest.mark.asyncio
-async def test_overrides_parent_has_no_such_method_async():
- class MyClassA:
- pass
-
- with pytest.raises(expected_exception=PedanticOverrideException):
- class MyClassB(MyClassA):
- @overrides(MyClassA)
- async def operation(self): return 42
-
-
@pytest.mark.asyncio
async def test_count_calls_async():
@count_calls
@@ -230,20 +117,6 @@ async def operation(i: int) -> str:
await operation(42)
-@pytest.mark.asyncio
-async def test_deprecated_async():
- @deprecated
- async def old_method(i: int) -> str:
- return str(i)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- await old_method(42)
- assert len(w) == 1
- assert issubclass(w[-1].category, DeprecationWarning)
- assert "deprecated" in str(w[-1].message)
-
-
@pytest.mark.asyncio
async def test_does_same_as_function_async():
async def some_method(x, y, z):
From 6b2eece8d94b08ee774895aea55bcdc1e224dffd Mon Sep 17 00:00:00 2001
From: Willi Sontopski <32729196+LostInDarkMath@users.noreply.github.com>
Date: Mon, 9 Feb 2026 21:00:24 +0100
Subject: [PATCH 3/3] update coveralls
---
.github/workflows/main.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 58639165..7d38642f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -27,7 +27,7 @@ jobs:
run: |
pytest --doctest-modules --cov=pedantic --cov-branch --cov-report= --cov-report=term
- name: Coveralls
- uses: coverallsapp/github-action@v2.2.3
+ uses: coverallsapp/github-action@v2.3.6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
file: .coverage