Skip to content

Commit 75216f7

Browse files
author
Andreas Hartl-Bachinger
committed
code cleanup, add make_validator decorator
1 parent 03be180 commit 75216f7

4 files changed

Lines changed: 81 additions & 11 deletions

File tree

decorator_validation/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .decorators import check_types # noqa
1+
from .decorators import check_types, make_validator # noqa
22
from .types import SkipTypeCheck # noqa
33
import decorator_validation.types
44
import decorator_validation.std_validators # noqa

decorator_validation/decorators.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from functools import wraps
2+
from typing import Callable, Any
23
import inspect
34
from .helpers import Annotation
45

56

6-
77
class check_types:
88
"""Decorator to automatically check input types of a function based on type annotation"""
99

@@ -54,3 +54,15 @@ def inner(*args, **kwargs):
5454
return func(*args, **kwargs)
5555

5656
return inner
57+
58+
59+
def make_validator(func: Callable[[Any], None]) -> Callable[[Any], bool]:
60+
"""takes in function that raises error for wrong type and makes it return
61+
True if no exception occurs"""
62+
63+
@wraps(func)
64+
def inner(*args, **kwargs):
65+
func(*args, **kwargs)
66+
return True
67+
68+
return inner
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
from typing import Union, Tuple, Iterable, Sequence
22
from pathlib import Path
3+
from .decorators import make_validator
34

45

5-
def is_file(file: Union[str, Path]) -> bool:
6+
@make_validator
7+
def is_file(file: Union[str, Path]) -> None:
68
"""check if file is a file"""
79
if not Path(file).resolve().is_file():
810
raise TypeError(f"File {file} does not exist!")
9-
return True
1011

1112

1213
def is_iterable_of(type_: Union[type, Tuple[type]]):
14+
@make_validator
1315
def check_fn(arg: Iterable):
1416
if not isinstance(arg, Iterable):
1517
raise TypeError("Argument has to be an iterable!")
@@ -19,12 +21,12 @@ def check_fn(arg: Iterable):
1921
f"Argument has to be an iterable with elements of type {type_},\
2022
but an element with type {type(a)} occured"
2123
)
22-
return True
2324

2425
return check_fn
2526

2627

2728
def is_sequence_of(type_: Union[type, Tuple[type]]):
29+
@make_validator
2830
def check_fn(arg: Sequence):
2931
if not isinstance(arg, Sequence):
3032
raise TypeError("Argument has to be an iterable!")
@@ -34,16 +36,12 @@ def check_fn(arg: Sequence):
3436
f"Argument has to be a sequence with elements of type {type_},\
3537
but an element with type {type(a)} occured"
3638
)
37-
return True
3839

3940
return check_fn
4041

4142

43+
@make_validator
4244
def is_num_as_str(number: str):
4345
if not isinstance(number, str):
4446
raise TypeError(f"{number} is not of type str but of type {type(number)}")
45-
try:
46-
float(number)
47-
return True
48-
except Exception:
49-
return False
47+
float(number) # fails if not possible

examples/basic_examples.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from decorator_validation import check_types
2+
3+
# check the types of your function
4+
5+
6+
@check_types
7+
def foo(a: int, b: int):
8+
return a * b
9+
10+
11+
foo(2, 2, 3.3) # returns 6
12+
foo(2.0, 3) # raises TypeError as argument a has no correct type
13+
14+
# to overwrite args pass in key mappings to the decorator.
15+
# Types have to be wrapped with a tuple
16+
17+
18+
@check_types(b=(int, float))
19+
def foo(a: int, b: int):
20+
return a * b
21+
22+
23+
foo(2, 3) # returns 6
24+
foo(2, 3.0) # returns 6.0
25+
foo(2.0, 3) # raises typeerror
26+
27+
# You can also use functions to validate certain inputs
28+
29+
from decorator_validation.std_validators import is_file
30+
31+
32+
@check_types(file=is_file)
33+
def write_to_file(file: str, text: str): ...
34+
35+
36+
write_to_file("non_existing_file.txt", "hello file") # will raise a type-error if file does not exist.
37+
38+
# You can also define custom "validators". Just make sure the function takes in the function input as an argument
39+
# and returns True if it is valid. In case it is'nt, raise an exception with a custom message or return False.
40+
41+
42+
def is_divisible_by_10(number: int | float):
43+
return number % 10 == 0
44+
45+
46+
# recommended version would use exceptions though for a more readable error msg
47+
def is_divisible_by_10_better(number: int | float):
48+
if not number % 10 == 0:
49+
raise ValueError("Input Argument is not divisible by 10")
50+
return True
51+
52+
53+
# for the above example you can use the utility decorator to make the code even more concise
54+
from decorator_validation import make_validator
55+
56+
57+
@make_validator
58+
def is_divisible_by_10_best(number: int | float):
59+
if not number % 10 == 0:
60+
raise ValueError("Input Argument is not divisible by 10")

0 commit comments

Comments
 (0)