diff --git a/CHANGELOG.md b/CHANGELOG.md index 961edb6..c447d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## Pedantic 2.1.7 +- add `transformation` parameter to `create_decorator` to make adding custom behavior easier + ## Pedantic 2.1.6 - Remove `inspect.getsource()` call from `@overrides` diff --git a/docs/pedantic/mixins/with_decorated_methods.html b/docs/pedantic/mixins/with_decorated_methods.html index 7d74b0d..123c874 100644 --- a/docs/pedantic/mixins/with_decorated_methods.html +++ b/docs/pedantic/mixins/with_decorated_methods.html @@ -50,13 +50,23 @@
pedantic.mixins.with_decorated_methodspedantic.mixins.with_decorated_methodsFunctions
-def create_decorator(decorator_type: DecoratorType) ‑> Callable[[~T], Callable[[~C], ~C]]
+def create_decorator(decorator_type: DecoratorType, transformation: Callable[[~C], ~C] = None) ‑> Callable[[~T], Callable[[~C], ~C]]
Creates a new decorator that is parametrized with one argument of an arbitrary type.
Creates a new decorator that is parametrized with one argument of an arbitrary type. +You can also pass an arbitrary [transformation] to add custom behavior to the decorator.
def create_decorator(decorator_type: DecoratorType) -> Callable[[T], Callable[[C], C]]:
- """ Creates a new decorator that is parametrized with one argument of an arbitrary type. """
+def create_decorator(
+ decorator_type: DecoratorType,
+ transformation: Callable[[C], C] = None,
+) -> Callable[[T], Callable[[C], C]]:
+ """
+ Creates a new decorator that is parametrized with one argument of an arbitrary type.
+ You can also pass an arbitrary [transformation] to add custom behavior to the decorator.
+ """
def decorator(value: T) -> Callable[[C], C]:
def fun(f: C) -> C:
setattr(f, decorator_type, value)
- return f
+
+ if transformation is None:
+ return f
+
+ return transformation(f)
return fun # we do not need functools.wraps, because we return the original function here
diff --git a/docs/pedantic/tests/test_with_decorated_methods.html b/docs/pedantic/tests/test_with_decorated_methods.html
index abb7340..9427f71 100644
--- a/docs/pedantic/tests/test_with_decorated_methods.html
+++ b/docs/pedantic/tests/test_with_decorated_methods.html
@@ -27,6 +27,7 @@ Module pedantic.tests.test_with_decorated_methods
Expand source code
import unittest
+from functools import wraps
from pedantic import DecoratorType, create_decorator, WithDecoratedMethods
@@ -91,7 +92,35 @@ Module pedantic.tests.test_with_decorated_methods
instance.m3: 44,
}
}
- assert instance.get_decorated_functions() == expected
+ assert instance.get_decorated_functions() == expected
+
+
+ def test_with_custom_transformation(self):
+ def my_transformation(f):
+ @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_custom_transformation(self)
+def test_with_custom_transformation(self):
+ def my_transformation(f):
+ @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_async(self)
TestWithDecoratedMethods
diff --git a/docs/pedantic/tests/tests_decorated_function.html b/docs/pedantic/tests/tests_decorated_function.html
index bff2a6a..c15768d 100644
--- a/docs/pedantic/tests/tests_decorated_function.html
+++ b/docs/pedantic/tests/tests_decorated_function.html
@@ -33,23 +33,19 @@ Module pedantic.tests.tests_decorated_functionModule pedantic.tests.tests_decorated_functionModule pedantic.tests.tests_decorated_functionModule pedantic.tests.tests_decorated_functionModule pedantic.tests.tests_decorated_functionClasses
class TestDecoratedFunction(unittest.TestCase):
def test_static_method(self):
- def f_1():
- pass
+ def f_1(): pass
deco_f = DecoratedFunction(f_1)
self.assertFalse(deco_f.is_static_method)
class MyClass:
- def f_1(self):
- pass
+ def f_1(self): pass
@staticmethod
- def f_2():
- pass
+ def f_2(): pass
@classmethod
- def f_3(cls):
- pass
+ def f_3(cls): pass
deco_f_1 = DecoratedFunction(MyClass.f_1)
deco_f_2 = DecoratedFunction(MyClass.f_2)
@@ -272,20 +239,15 @@ Classes
self.assertFalse(deco_f_3.is_static_method)
def test_function_wants_args(self):
- def f_1(*args, **kwargs):
- pass
+ def f_1(*args, **kwargs): pass
- def f_2(a, b, *args, **kwargs):
- pass
+ def f_2(a, b, *args, **kwargs): pass
- def f_3(a, b, *args):
- pass
+ def f_3(a, b, *args): pass
- def f_4(*args):
- pass
+ def f_4(*args): pass
- def f_5():
- pass
+ def f_5(): pass
self.assertTrue(DecoratedFunction(f_1).wants_args)
self.assertTrue(DecoratedFunction(f_2).wants_args)
@@ -294,53 +256,42 @@ Classes
self.assertFalse(DecoratedFunction(f_5).wants_args)
class MyClass:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ 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
+ def f_1(): pass
self.assertFalse(DecoratedFunction(f_1).is_property_setter)
class MyClass:
_h = 42
- def f_1(self):
- pass
+ def f_1(self): pass
@staticmethod
- def f_2():
- pass
+ 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_1(*args, **kwargs): pass
- def f_2(a, b, *args, **kwargs):
- pass
+ def f_2(a, b, *args, **kwargs): pass
- def f_3(a, b, *args):
- pass
+ def f_3(a, b, *args): pass
- def f_4(*args):
- pass
+ def f_4(*args): pass
- def f_5():
- pass
+ def f_5(): pass
- def f_6(a, b, c):
- pass
+ def f_6(a, b, c): pass
self.assertFalse(DecoratedFunction(f_1).should_have_kwargs)
self.assertFalse(DecoratedFunction(f_2).should_have_kwargs)
@@ -350,33 +301,27 @@ Classes
self.assertTrue(DecoratedFunction(f_6).should_have_kwargs)
class A:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ def g(): pass
- def __compare__(self, other):
- 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
+ def h(): pass
self.assertFalse(DecoratedFunction(h).is_instance_method)
class A:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ def g(): pass
self.assertTrue(DecoratedFunction(A.f).is_instance_method)
self.assertFalse(DecoratedFunction(A.g).is_instance_method)
@@ -385,17 +330,14 @@ Classes
def decorator(f):
return f
- def f_1():
- pass
+ def f_1(): pass
@decorator
- def f_2():
- pass
+ def f_2(): pass
@decorator
@decorator
- def f_3():
- pass
+ def f_3(): pass
@decorator
@decorator
@@ -424,20 +366,15 @@ Methods
Expand source code
def test_function_wants_args(self):
- def f_1(*args, **kwargs):
- pass
+ def f_1(*args, **kwargs): pass
- def f_2(a, b, *args, **kwargs):
- pass
+ def f_2(a, b, *args, **kwargs): pass
- def f_3(a, b, *args):
- pass
+ def f_3(a, b, *args): pass
- def f_4(*args):
- pass
+ def f_4(*args): pass
- def f_5():
- pass
+ def f_5(): pass
self.assertTrue(DecoratedFunction(f_1).wants_args)
self.assertTrue(DecoratedFunction(f_2).wants_args)
@@ -446,12 +383,10 @@ Methods
self.assertFalse(DecoratedFunction(f_5).wants_args)
class MyClass:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ def g(): pass
self.assertFalse(DecoratedFunction(MyClass.f).wants_args)
self.assertFalse(DecoratedFunction(MyClass.g).wants_args)
@@ -467,18 +402,15 @@ Methods
Expand source code
def test_instance_method(self):
- def h():
- pass
+ def h(): pass
self.assertFalse(DecoratedFunction(h).is_instance_method)
class A:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ def g(): pass
self.assertTrue(DecoratedFunction(A.f).is_instance_method)
self.assertFalse(DecoratedFunction(A.g).is_instance_method)
@@ -494,20 +426,17 @@ Methods
Expand source code
def test_is_property_setter(self):
- def f_1():
- pass
+ def f_1(): pass
self.assertFalse(DecoratedFunction(f_1).is_property_setter)
class MyClass:
_h = 42
- def f_1(self):
- pass
+ def f_1(self): pass
@staticmethod
- def f_2():
- pass
+ def f_2(): pass
self.assertFalse(DecoratedFunction(MyClass.f_1).is_property_setter)
self.assertFalse(DecoratedFunction(MyClass.f_2).is_property_setter)
@@ -526,17 +455,14 @@ Methods
def decorator(f):
return f
- def f_1():
- pass
+ def f_1(): pass
@decorator
- def f_2():
- pass
+ def f_2(): pass
@decorator
@decorator
- def f_3():
- pass
+ def f_3(): pass
@decorator
@decorator
@@ -560,23 +486,19 @@ Methods
Expand source code
def test_static_method(self):
- def f_1():
- pass
+ def f_1(): pass
deco_f = DecoratedFunction(f_1)
self.assertFalse(deco_f.is_static_method)
class MyClass:
- def f_1(self):
- pass
+ def f_1(self): pass
@staticmethod
- def f_2():
- pass
+ def f_2(): pass
@classmethod
- def f_3(cls):
- pass
+ def f_3(cls): pass
deco_f_1 = DecoratedFunction(MyClass.f_1)
deco_f_2 = DecoratedFunction(MyClass.f_2)
@@ -597,23 +519,17 @@ Methods
Expand source code
def test_wants_kwargs(self):
- def f_1(*args, **kwargs):
- pass
+ def f_1(*args, **kwargs): pass
- def f_2(a, b, *args, **kwargs):
- pass
+ def f_2(a, b, *args, **kwargs): pass
- def f_3(a, b, *args):
- pass
+ def f_3(a, b, *args): pass
- def f_4(*args):
- pass
+ def f_4(*args): pass
- def f_5():
- pass
+ def f_5(): pass
- def f_6(a, b, c):
- pass
+ def f_6(a, b, c): pass
self.assertFalse(DecoratedFunction(f_1).should_have_kwargs)
self.assertFalse(DecoratedFunction(f_2).should_have_kwargs)
@@ -623,15 +539,12 @@ Methods
self.assertTrue(DecoratedFunction(f_6).should_have_kwargs)
class A:
- def f(self):
- pass
+ def f(self): pass
@staticmethod
- def g():
- pass
+ def g(): pass
- def __compare__(self, other):
- pass
+ def __compare__(self, other): pass
self.assertTrue(DecoratedFunction(A.f).should_have_kwargs)
self.assertTrue(DecoratedFunction(A.g).should_have_kwargs)
diff --git a/docs/pedantic/tests/tests_main.html b/docs/pedantic/tests/tests_main.html
index 480927f..7936e7a 100644
--- a/docs/pedantic/tests/tests_main.html
+++ b/docs/pedantic/tests/tests_main.html
@@ -32,7 +32,7 @@ Module pedantic.tests.tests_main
sys.path.append(os.getcwd())
-from pedantic.tests.test_retry import TestRetry
+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
@@ -103,6 +103,7 @@ Module pedantic.tests.tests_main
TestGeneratorWrapper,
TestRenameKwargs,
TestRetry,
+ TestRetryFunc,
TestResolveForwardRef,
# validate
TestValidatorDatetimeIsoformat,
@@ -189,6 +190,7 @@ Functions
TestGeneratorWrapper,
TestRenameKwargs,
TestRetry,
+ TestRetryFunc,
TestResolveForwardRef,
# validate
TestValidatorDatetimeIsoformat,
diff --git a/docs/pedantic/tests/tests_small_method_decorators.html b/docs/pedantic/tests/tests_small_method_decorators.html
index 9e47146..fca3839 100644
--- a/docs/pedantic/tests/tests_small_method_decorators.html
+++ b/docs/pedantic/tests/tests_small_method_decorators.html
@@ -29,6 +29,7 @@ Module pedantic.tests.tests_small_method_decoratorsimport 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, \
@@ -44,13 +45,11 @@ Module pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsModule pedantic.tests.tests_small_method_decoratorsClasses
class AsyncSmallDecoratorTests(IsolatedAsyncioTestCase):
async def test_overrides_async_instance_method(self) -> None:
class MyClassA:
- async def operation(self):
- pass
+ async def operation(self): pass
class MyClassB(MyClassA):
@overrides(MyClassA)
@@ -380,8 +371,7 @@ Classes
with self.assertRaises(expected_exception=PedanticOverrideException):
class MyClassB(MyClassA):
@overrides(MyClassA)
- async def operation(self):
- return 42
+ async def operation(self): return 42
async def test_count_calls_async(self):
@count_calls
@@ -471,9 +461,7 @@ Classes
async def test_mock_async(self) -> None:
@mock(return_value=42)
- async def my_function(a, b, c):
- await asyncio.sleep(0)
- return a + b + c
+ 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
@@ -617,9 +605,7 @@ Methods
async def test_mock_async(self) -> None:
@mock(return_value=42)
- async def my_function(a, b, c):
- await asyncio.sleep(0)
- return a + b + c
+ 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
@@ -636,8 +622,7 @@ Methods
async def test_overrides_async_instance_method(self) -> None:
class MyClassA:
- async def operation(self):
- pass
+ async def operation(self): pass
class MyClassB(MyClassA):
@overrides(MyClassA)
@@ -665,8 +650,7 @@ Methods
with self.assertRaises(expected_exception=PedanticOverrideException):
class MyClassB(MyClassA):
@overrides(MyClassA)
- async def operation(self):
- return 42
+ async def operation(self): return 42
@@ -791,13 +775,11 @@ Methods
with self.assertRaises(expected_exception=PedanticOverrideException):
class MyClassB(MyClassA):
@overrides(MyClassA)
- def operation(self):
- return 42
+ def operation(self): pass
def test_overrides_all_good(self):
class MyClassA:
- def operation(self):
- pass
+ def operation(self): pass
class MyClassB(MyClassA):
@overrides(MyClassA)
@@ -810,8 +792,7 @@ Methods
def test_overrides_static_method(self):
class MyClassA:
@staticmethod
- def operation():
- pass
+ def operation(): pass
class MyClassB(MyClassA):
@staticmethod
@@ -826,8 +807,8 @@ Methods
def test_overrides_below_property(self):
class MyClassA:
@property
- def operation(self):
- return 42
+ @abstractmethod
+ def operation(self): pass
class MyClassB(MyClassA):
@property
@@ -844,13 +825,11 @@ Methods
with self.assertRaises(expected_exception=PedanticOverrideException):
@overrides(MyClassA)
- def operation():
- return 42
+ def operation(): return 42
def test_deprecated_1(self):
@deprecated
- def old_method(i: int) -> str:
- return str(i)
+ def old_method(i: int) -> str: return str(i)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
@@ -961,8 +940,7 @@ Methods
def test_deprecated_1(self):
@deprecated
- def old_method(i: int) -> str:
- return str(i)
+ def old_method(i: int) -> str: return str(i)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
@@ -1045,8 +1023,7 @@ Methods
def test_overrides_all_good(self):
class MyClassA:
- def operation(self):
- pass
+ def operation(self): pass
class MyClassB(MyClassA):
@overrides(MyClassA)
@@ -1069,8 +1046,8 @@ Methods
def test_overrides_below_property(self):
class MyClassA:
@property
- def operation(self):
- return 42
+ @abstractmethod
+ def operation(self): pass
class MyClassB(MyClassA):
@property
@@ -1097,8 +1074,7 @@ Methods
with self.assertRaises(expected_exception=PedanticOverrideException):
@overrides(MyClassA)
- def operation():
- return 42
+ def operation(): return 42
@@ -1117,8 +1093,7 @@ Methods
with self.assertRaises(expected_exception=PedanticOverrideException):
class MyClassB(MyClassA):
@overrides(MyClassA)
- def operation(self):
- return 42
+ def operation(self): pass
@@ -1133,8 +1108,7 @@ Methods
def test_overrides_static_method(self):
class MyClassA:
@staticmethod
- def operation():
- pass
+ def operation(): pass
class MyClassB(MyClassA):
@staticmethod
diff --git a/docs/pedantic/tests/validate/test_validator_is_enum.html b/docs/pedantic/tests/validate/test_validator_is_enum.html
index 1fce569..68a9b09 100644
--- a/docs/pedantic/tests/validate/test_validator_is_enum.html
+++ b/docs/pedantic/tests/validate/test_validator_is_enum.html
@@ -95,8 +95,7 @@ Module pedantic.tests.validate.test_validator_is_enumClass variables
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):
- return x
+ def foo(x): print(x)
for value in ['red', 'blue', 'Red', 'bLUe']:
with self.assertRaises(expected_exception=ParameterException):
@@ -422,8 +420,7 @@ Methods
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):
- return x
+ def foo(x): print(x)
for value in ['red', 'blue', 'Red', 'bLUe']:
with self.assertRaises(expected_exception=ParameterException):
diff --git a/pedantic/mixins/with_decorated_methods.py b/pedantic/mixins/with_decorated_methods.py
index cc68528..3ab8f0d 100644
--- a/pedantic/mixins/with_decorated_methods.py
+++ b/pedantic/mixins/with_decorated_methods.py
@@ -22,13 +22,23 @@ class DecoratorType(StrEnum):
C = TypeVar('C', bound=Callable)
-def create_decorator(decorator_type: DecoratorType) -> Callable[[T], Callable[[C], C]]:
- """ Creates a new decorator that is parametrized with one argument of an arbitrary type. """
+def create_decorator(
+ decorator_type: DecoratorType,
+ transformation: Callable[[C], C] = None,
+) -> Callable[[T], Callable[[C], C]]:
+ """
+ Creates a new decorator that is parametrized with one argument of an arbitrary type.
+ You can also pass an arbitrary [transformation] to add custom behavior to the decorator.
+ """
def decorator(value: T) -> Callable[[C], C]:
def fun(f: C) -> C:
setattr(f, decorator_type, value)
- return f
+
+ if transformation is None:
+ return f
+
+ return transformation(f)
return fun # we do not need functools.wraps, because we return the original function here
diff --git a/pedantic/tests/test_with_decorated_methods.py b/pedantic/tests/test_with_decorated_methods.py
index b92a3c4..a1744b8 100644
--- a/pedantic/tests/test_with_decorated_methods.py
+++ b/pedantic/tests/test_with_decorated_methods.py
@@ -1,4 +1,5 @@
import unittest
+from functools import wraps
from pedantic import DecoratorType, create_decorator, WithDecoratedMethods
@@ -64,3 +65,31 @@ async def m3(self) -> None:
}
}
assert instance.get_decorated_functions() == expected
+
+
+ def test_with_custom_transformation(self):
+ def my_transformation(f):
+ @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
diff --git a/setup.py b/setup.py
index 7217121..0d629f1 100644
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,7 @@ def get_content_from_readme(file_name: str = 'README.md') -> str:
setup(
name="pedantic",
- version="2.1.6",
+ version="2.1.7",
python_requires='>=3.11.0',
packages=find_packages(),
install_requires=[],