-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtypes.py
188 lines (145 loc) · 6.52 KB
/
types.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from generallibrary.iterables import depth, iter_first_value, is_iterable
def strToDynamicType(var):
"""
Try to convert a string to bool, None, int or float.
If failed then it returns the given var.
:param any var: Object to be converted
:return: Converted string or original string if failed
:raises TypeError: If var is not a string
"""
var = str(var)
keyWords = {"true": True, "false": False, "none": None}
if var.lower() in keyWords:
return keyWords[var.lower()]
# int() doesn't declare 'raises' in doc type so catch everything
try:
return int(var)
except (ValueError, TypeError):
pass
# float() doesn't declare 'raises' in doc type so catch everything
try:
return float(var)
except (ValueError, TypeError):
pass
return var
def _typeChecker_checkObject(obj, types, literalObjects):
# objDepth = depth(obj)
typesDepth = len(types) - 1
# if objDepth != typesDepth:
# raise TypeError(f"Obj depth {objDepth} doesnt match types depth {typesDepth}")
for i, typeTuple in enumerate(types):
# Returned ValueError if obj was pandas.DataFrame, so there are probably more objects that can raise any error
# So catch every exception, it's a pretty simple statement so not too big of a problem
for literalObj in literalObjects:
if obj is literalObj:
objInLiteralObjects = True
break
else:
objInLiteralObjects = False
if objInLiteralObjects:
if obj not in typeTuple:
raise TypeError(f"obj {obj} was a literal object in {literalObjects} but not in {typeTuple} in depth {i}/{typesDepth}")
else:
typeTupleWithoutLiteralObjects = tuple([t for t in typeTuple if t not in literalObjects])
typeTupleWithOnlyTypes = tuple([t for t in typeTupleWithoutLiteralObjects if not isinstance(t, str)])
typeTupleWithOnlyStrings = tuple([t for t in typeTuple if isinstance(t, str)])
objTypeInList = isinstance(obj, typeTupleWithOnlyTypes)
objClassNameInList = False
for className in getBaseClassNames(obj, includeSelf=True):
if isinstance(obj, bool) and className == "int":
continue
if className in typeTupleWithOnlyStrings:
objClassNameInList = True
break
# Because isinstance(False, int) = True
isBoolAndBoolOrObjectNotInList = isinstance(obj, bool) and bool not in typeTupleWithOnlyTypes and object not in typeTupleWithOnlyTypes and not objClassNameInList
if isBoolAndBoolOrObjectNotInList or not (objTypeInList or objClassNameInList):
raise TypeError(f"obj {obj} wasn't type {typeTuple} in depth {i}/{typesDepth}")
if is_iterable(obj):
obj = iter_first_value(obj)
# elif i < objDepth:
# raise TypeError(f"obj {obj} is not iterable but atleast one more subtype is required in depth {i}/{typesDepth}")
def _typeChecker_prepareTypesList(types, literalObjects):
newTypes = []
for argType in types:
if isinstance(argType, (list, tuple, set)):
newArgType = list(argType)
else:
isType = isinstance(argType, type)
isLiteralObject = argType in literalObjects
isNameOfClass = isinstance(argType, str)
if isType or isLiteralObject or isNameOfClass:
newArgType = [argType]
else:
raise TypeError(f"Argument type {argType} is not a list, tuple, set, type or literalObject")
newArgTypeWithOnlyStrings = tuple([t for t in newArgType if isinstance(t, str)])
floatInList = float in newArgType or "float" in newArgTypeWithOnlyStrings
intInList = int in newArgType or "int" in newArgTypeWithOnlyStrings
if floatInList and not intInList:
newArgType.append(int)
newTypes.append(tuple(newArgType))
return newTypes
def typeChecker(obj, *types, error=True):
"""
Check type(s) of an object.
The first type correlates to the first layer of obj and so on.
Each type can be a (tuple that holds) type, string or literal object such as `None`.
:param obj: Generic obj, iterable or not
:param types: lists or tuples if obj at that level can be multiple types, single type if only one
:param error: Raise error if true, otherwise returns False when fail
:return:
"""
literalObjects = [None]
try:
if not types:
raise TypeError("No types were given as args")
types = _typeChecker_prepareTypesList(types, literalObjects)
_typeChecker_checkObject(obj, types, literalObjects)
except TypeError as e:
if error:
raise e
else:
return False
else:
return True
def getBaseClasses(obj, includeSelf=False, includeObject=True, includeInstance=False):
""" Recursively get all base classes from an object's class.
:param any obj: Generic obj or class
:param includeSelf: Whether to include own class or not
:param includeObject: Whether to include object class or not (Every object has object as base)
:param includeInstance: Whether to include instance if obj is instance.
:return: List of classes
:rtype: list[type] """
if isinstance(obj, type):
cls = obj
else:
cls = obj.__class__
classes = list(cls.__bases__)
for base in classes:
for baseClassBase in getBaseClasses(base):
if baseClassBase not in classes:
classes.append(baseClassBase)
if includeSelf:
classes.insert(0, cls)
if not includeObject:
classes.remove(object)
if includeInstance and obj is not cls and obj not in classes:
classes.insert(0, obj)
return classes
def getBaseClassNames(obj, includeSelf=False, includeObject=True):
"""
Get all base classes from an object's class.
:param any obj: Generic obj or class
:param includeSelf: Whether to include own class name or not
:return: List of lowered class names
:rtype: list[str]
"""
return [cls.__name__ for cls in getBaseClasses(obj=obj, includeSelf=includeSelf, includeObject=includeObject)]
def hasMethod(obj, method):
"""
Return whether an object has a specific callabale attribute.
:param object obj: Any object
:param str method: String of method to check
"""
attr = getattr(obj, method, False)
return attr and callable(attr)