-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexample_7.py
131 lines (106 loc) · 4.52 KB
/
example_7.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Example 7
from datetime import datetime
from functools import wraps
import typing
from validation import validate_type
def validate_method_arguments(func, args, kwargs):
types_dict = func.__annotations__.copy()
types_dict.pop('return', None)
# Validate argument types
for index, (attr, attr_type) in enumerate(types_dict.items()):
if attr in kwargs:
validate_type(attr_type.type, kwargs[attr], attr)
elif len(args) - 1 >= index:
validate_type(attr_type.type, args[index], attr)
else:
validate_type(attr_type.type, None, attr)
def validate_return(func, return_value):
types_dict = func.__annotations__
return_type = types_dict['return']
# Validate return type
if return_value:
validate_type(return_type.type, return_value, 'return_value')
def extend_docstring(type_annot, spaces=4):
docs = []
docs.extend(
(':param {}: {}'.format(attr, attr_type.description)
for (attr, attr_type) in type_annot.items() if attr != 'return')
)
docs.extend(
(':type {}: {}'.format(attr, attr_type.type)
for (attr, attr_type) in type_annot.items() if attr != 'return')
)
# Add return type if exist
if 'return' in type_annot:
docs.extend([':return: {}'.format(type_annot['return'].description),
':rtype: {}'.format(type_annot['return'].type)])
return ('\n' + ' ' * spaces).join(docs)
class Type:
def __init__(self, type, description=None):
self.description = description
self.type = type
def add_function_docs(wrapper, func):
type_annot = func.__annotations__
func_doc = func.__doc__ or '\n '
wrapper.__doc__ = func_doc + extend_docstring(type_annot, spaces=8)
return wrapper
def validate_class_method_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
validate_method_arguments(func, args, kwargs)
return_value = func(self, *args, **kwargs)
validate_return(func, return_value)
return return_value
return add_function_docs(wrapper, func)
class StrongTyping(type):
@staticmethod
def _add_attribute(cls, property_name, property_type):
def set_attribute(self, property_value):
validate_type(property_type.type, property_value, property_name)
setattr(self.__class__, '_' + property_name, property_value)
def get_attribute(self):
return getattr(self, '_' + property_name, None)
setattr(cls, property_name, property(get_attribute, set_attribute))
def __init__(cls, name, bases, dct):
# Add decorators to each class method to validate args and return value
for name, obj in dct.items():
# Only decorate class methods
if hasattr(obj, '__call__'):
setattr(cls, name, validate_class_method_decorator(obj))
# Validate class attributes - create settter/getter properties
for key, val in typing.get_type_hints(cls).items():
StrongTyping._add_attribute(cls, key, val)
super(StrongTyping, cls).__init__(name, bases, dct)
def __new__(cls, name, bases, clsdict):
type_annot = clsdict.get('__annotations__', {})
class_doc = clsdict.get('__doc__', '\n ')
clsdict['__doc__'] = class_doc + extend_docstring(type_annot)
# Extend doc-strings
return super(StrongTyping, cls).__new__(cls, name, bases, clsdict)
class Payment(metaclass=StrongTyping):
"""
Payment object defines funds transfer event.
"""
amount: Type(float, "Payment amount")
account_id: Type(str, "Target account ID")
timestamp: Type(float, "Payment timestamp")
def __init__(self, amount: Type(float, "Payment amount"),
account_number: Type(int, "Target account number"),
internal: Type(bool, "Is account internal or external?")) -> Type(None, ""):
"""
Payment object constructor method
"""
self.account_id = '{}_{}'.format('INTERNAL' if internal else 'EXTERNAL', account_number)
self.amount = amount
self.timestamp = datetime.now().timestamp()
def get_status(
self, timestamp: Type(datetime, "Payment validation timestamp")
) -> Type(str, "Payment status at given timestamp"):
"""
Get payment event status
"""
return 'PENDING' if self.timestamp > timestamp.timestamp() else 'COMMITTED'
if __name__ == '__main__':
print(Payment.__doc__)
print(Payment.__init__.__doc__)
print(Payment.get_status.__doc__)