1import re
2import inspect
3import builtins
4
5_SAFE_BUILTINS = {name: obj for name, obj in vars(builtins).items() if isinstance(obj, type)}
6
7
8def _default_for(type_str):
9 type_str = type_str.strip()
10
11 if '|' in type_str:
12 return _default_for(type_str.split('|')[0])
13
14 match = re.match(r'^(\w+)\[', type_str)
15 type_name = match.group(1) if match else type_str
16
17 if type_name == 'None':
18 return None
19
20 try: # Code Injection - Try me bitch
21 return eval(type_name, {"__builtins__": _SAFE_BUILTINS})()
22 except Exception as e:
23 raise AttributeError(f"'{type_name}' is not a valid built-in type") from e
24
25
26def _parse_params(docstring):
27 if not docstring:
28 return []
29
30 docstring = inspect.cleandoc(docstring)
31 match = re.search(r'Parameters\n-{10}\n(.*?)(\n\n|$)', docstring, re.DOTALL)
32
33 if not match:
34 return []
35
36 variable_block = match.group(1)
37 # Returns list of the types of all variables
38 return re.findall(r'^\w+\s*:\s*(.+)$', variable_block, re.MULTILINE)
39
40
41class FilteredModule:
42 def __init__(self, module):
43 self.__module = module
44
45 def __getattr__(self, name):
46 if not hasattr(self.__module, name):
47 raise AttributeError(f"Module has no attribute '{name}'")
48
49 obj = getattr(self.__module, name)
50
51 self.__check_name(obj)
52 self.__check_callable(obj)
53 self.__check_attribute(obj)
54
55 return obj
56
57 @staticmethod
58 def __check_name(obj):
59 if not hasattr(obj, '__name__') or not obj.__name__[0].isupper():
60 raise AttributeError(
61 f"'{getattr(obj, '__name__', repr(obj))}' failed question 1: __name__ must start with uppercase")
62
63 @staticmethod
64 def __check_callable(obj):
65 params = _parse_params(obj.__doc__)
66 args = [_default_for(type_str) for type_str in params]
67
68 try:
69 obj(*args)
70 except Exception as e:
71 raise AttributeError(f"'{obj.__name__}' failed question 2: call failed or not callable") from e
72
73 @staticmethod
74 def __check_attribute(obj):
75 for attr in vars(obj).keys():
76 # Skip if dunder OR contains 4+ consecutive vowels OR last letter isn't uppercase
77 if (re.search(r'^__.*__$', attr) or re.search(r'[aeiouAEIOU]{4,}', attr) or
78 not re.search(r'[A-Z][^a-zA-Z]*$', attr)):
79 continue
80 return
81
82 raise AttributeError(f"'{obj.__name__}' failed question 3: no valid attribute found")
83
84
85class BridgeKeeper:
86 def __init__(self, module_name):
87 self.module_name = module_name
88
89 def __enter__(self):
90 self.module = __import__(self.module_name)
91 return FilteredModule(self.module)
92
93 def __exit__(self, exc_type, exc_val, exc_tb):
94 return False
............F.F..F...F.FF
======================================================================
FAIL: test_rejects_callable_when_parameter_names_do_not_match (test.TestBridgeKeeper.test_rejects_callable_when_parameter_names_do_not_match)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 59, in test_rejects_callable_when_parameter_names_do_not_match
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
======================================================================
FAIL: test_rejects_dict_when_callable_does_not_work_with_documented_key_and_value_types (test.TestBridgeKeeper.test_rejects_dict_when_callable_does_not_work_with_documented_key_and_value_types)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 90, in test_rejects_dict_when_callable_does_not_work_with_documented_key_and_value_types
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
======================================================================
FAIL: test_rejects_list_when_callable_does_not_work_with_documented_element_type (test.TestBridgeKeeper.test_rejects_list_when_callable_does_not_work_with_documented_element_type)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 102, in test_rejects_list_when_callable_does_not_work_with_documented_element_type
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
======================================================================
FAIL: test_rejects_set_when_callable_does_not_work_with_documented_element_type (test.TestBridgeKeeper.test_rejects_set_when_callable_does_not_work_with_documented_element_type)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 112, in test_rejects_set_when_callable_does_not_work_with_documented_element_type
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
======================================================================
FAIL: test_rejects_tuple_when_callable_does_not_work_with_documented_element_type (test.TestBridgeKeeper.test_rejects_tuple_when_callable_does_not_work_with_documented_element_type)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 107, in test_rejects_tuple_when_callable_does_not_work_with_documented_element_type
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
======================================================================
FAIL: test_rejects_union_when_callable_does_not_support_all_union_branches (test.TestBridgeKeeper.test_rejects_union_when_callable_does_not_support_all_union_branches)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 72, in test_rejects_union_when_callable_does_not_support_all_union_branches
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
----------------------------------------------------------------------
Ran 25 tests in 0.003s
FAILED (failures=6)
| f | 1 | import re | f | 1 | import re |
| 2 | import inspect | 2 | import inspect | ||
| 3 | import builtins | 3 | import builtins | ||
| 4 | 4 | ||||
| 5 | _SAFE_BUILTINS = {name: obj for name, obj in vars(builtins).items() if isinstance(obj, type)} | 5 | _SAFE_BUILTINS = {name: obj for name, obj in vars(builtins).items() if isinstance(obj, type)} | ||
| 6 | 6 | ||||
| 7 | 7 | ||||
| 8 | def _default_for(type_str): | 8 | def _default_for(type_str): | ||
| 9 | type_str = type_str.strip() | 9 | type_str = type_str.strip() | ||
| 10 | 10 | ||||
| 11 | if '|' in type_str: | 11 | if '|' in type_str: | ||
| 12 | return _default_for(type_str.split('|')[0]) | 12 | return _default_for(type_str.split('|')[0]) | ||
| 13 | 13 | ||||
| 14 | match = re.match(r'^(\w+)\[', type_str) | 14 | match = re.match(r'^(\w+)\[', type_str) | ||
| 15 | type_name = match.group(1) if match else type_str | 15 | type_name = match.group(1) if match else type_str | ||
| 16 | 16 | ||||
| 17 | if type_name == 'None': | 17 | if type_name == 'None': | ||
| 18 | return None | 18 | return None | ||
| 19 | 19 | ||||
| 20 | try: # Code Injection - Try me bitch | 20 | try: # Code Injection - Try me bitch | ||
| 21 | return eval(type_name, {"__builtins__": _SAFE_BUILTINS})() | 21 | return eval(type_name, {"__builtins__": _SAFE_BUILTINS})() | ||
| 22 | except Exception as e: | 22 | except Exception as e: | ||
| 23 | raise AttributeError(f"'{type_name}' is not a valid built-in type") from e | 23 | raise AttributeError(f"'{type_name}' is not a valid built-in type") from e | ||
| 24 | 24 | ||||
| 25 | 25 | ||||
| 26 | def _parse_params(docstring): | 26 | def _parse_params(docstring): | ||
| 27 | if not docstring: | 27 | if not docstring: | ||
| 28 | return [] | 28 | return [] | ||
| 29 | 29 | ||||
| 30 | docstring = inspect.cleandoc(docstring) | 30 | docstring = inspect.cleandoc(docstring) | ||
| t | 31 | match = re.search(r'Parameters\n----------\n(.*?)(\n\n|$)', docstring, re.DOTALL) | t | 31 | match = re.search(r'Parameters\n-{10}\n(.*?)(\n\n|$)', docstring, re.DOTALL) |
| 32 | 32 | ||||
| 33 | if not match: | 33 | if not match: | ||
| 34 | return [] | 34 | return [] | ||
| 35 | 35 | ||||
| 36 | variable_block = match.group(1) | 36 | variable_block = match.group(1) | ||
| 37 | # Returns list of the types of all variables | 37 | # Returns list of the types of all variables | ||
| 38 | return re.findall(r'^\w+\s*:\s*(.+)$', variable_block, re.MULTILINE) | 38 | return re.findall(r'^\w+\s*:\s*(.+)$', variable_block, re.MULTILINE) | ||
| 39 | 39 | ||||
| 40 | 40 | ||||
| 41 | class FilteredModule: | 41 | class FilteredModule: | ||
| 42 | def __init__(self, module): | 42 | def __init__(self, module): | ||
| 43 | self.__module = module | 43 | self.__module = module | ||
| 44 | 44 | ||||
| 45 | def __getattr__(self, name): | 45 | def __getattr__(self, name): | ||
| 46 | if not hasattr(self.__module, name): | 46 | if not hasattr(self.__module, name): | ||
| 47 | raise AttributeError(f"Module has no attribute '{name}'") | 47 | raise AttributeError(f"Module has no attribute '{name}'") | ||
| 48 | 48 | ||||
| 49 | obj = getattr(self.__module, name) | 49 | obj = getattr(self.__module, name) | ||
| 50 | 50 | ||||
| 51 | self.__check_name(obj) | 51 | self.__check_name(obj) | ||
| 52 | self.__check_callable(obj) | 52 | self.__check_callable(obj) | ||
| 53 | self.__check_attribute(obj) | 53 | self.__check_attribute(obj) | ||
| 54 | 54 | ||||
| 55 | return obj | 55 | return obj | ||
| 56 | 56 | ||||
| 57 | @staticmethod | 57 | @staticmethod | ||
| 58 | def __check_name(obj): | 58 | def __check_name(obj): | ||
| 59 | if not hasattr(obj, '__name__') or not obj.__name__[0].isupper(): | 59 | if not hasattr(obj, '__name__') or not obj.__name__[0].isupper(): | ||
| 60 | raise AttributeError( | 60 | raise AttributeError( | ||
| 61 | f"'{getattr(obj, '__name__', repr(obj))}' failed question 1: __name__ must start with uppercase") | 61 | f"'{getattr(obj, '__name__', repr(obj))}' failed question 1: __name__ must start with uppercase") | ||
| 62 | 62 | ||||
| 63 | @staticmethod | 63 | @staticmethod | ||
| 64 | def __check_callable(obj): | 64 | def __check_callable(obj): | ||
| 65 | params = _parse_params(obj.__doc__) | 65 | params = _parse_params(obj.__doc__) | ||
| 66 | args = [_default_for(type_str) for type_str in params] | 66 | args = [_default_for(type_str) for type_str in params] | ||
| 67 | 67 | ||||
| 68 | try: | 68 | try: | ||
| 69 | obj(*args) | 69 | obj(*args) | ||
| 70 | except Exception as e: | 70 | except Exception as e: | ||
| 71 | raise AttributeError(f"'{obj.__name__}' failed question 2: call failed or not callable") from e | 71 | raise AttributeError(f"'{obj.__name__}' failed question 2: call failed or not callable") from e | ||
| 72 | 72 | ||||
| 73 | @staticmethod | 73 | @staticmethod | ||
| 74 | def __check_attribute(obj): | 74 | def __check_attribute(obj): | ||
| 75 | for attr in vars(obj).keys(): | 75 | for attr in vars(obj).keys(): | ||
| 76 | # Skip if dunder OR contains 4+ consecutive vowels OR last letter isn't uppercase | 76 | # Skip if dunder OR contains 4+ consecutive vowels OR last letter isn't uppercase | ||
| 77 | if (re.search(r'^__.*__$', attr) or re.search(r'[aeiouAEIOU]{4,}', attr) or | 77 | if (re.search(r'^__.*__$', attr) or re.search(r'[aeiouAEIOU]{4,}', attr) or | ||
| 78 | not re.search(r'[A-Z][^a-zA-Z]*$', attr)): | 78 | not re.search(r'[A-Z][^a-zA-Z]*$', attr)): | ||
| 79 | continue | 79 | continue | ||
| 80 | return | 80 | return | ||
| 81 | 81 | ||||
| 82 | raise AttributeError(f"'{obj.__name__}' failed question 3: no valid attribute found") | 82 | raise AttributeError(f"'{obj.__name__}' failed question 3: no valid attribute found") | ||
| 83 | 83 | ||||
| 84 | 84 | ||||
| 85 | class BridgeKeeper: | 85 | class BridgeKeeper: | ||
| 86 | def __init__(self, module_name): | 86 | def __init__(self, module_name): | ||
| 87 | self.module_name = module_name | 87 | self.module_name = module_name | ||
| 88 | 88 | ||||
| 89 | def __enter__(self): | 89 | def __enter__(self): | ||
| 90 | self.module = __import__(self.module_name) | 90 | self.module = __import__(self.module_name) | ||
| 91 | return FilteredModule(self.module) | 91 | return FilteredModule(self.module) | ||
| 92 | 92 | ||||
| 93 | def __exit__(self, exc_type, exc_val, exc_tb): | 93 | def __exit__(self, exc_type, exc_val, exc_tb): | ||
| 94 | return False | 94 | return False |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | import re | f | 1 | import re |
| 2 | import inspect | 2 | import inspect | ||
| 3 | import builtins | 3 | import builtins | ||
| 4 | 4 | ||||
| t | 5 | _SAFE_BUILTINS = { | t | 5 | _SAFE_BUILTINS = {name: obj for name, obj in vars(builtins).items() if isinstance(obj, type)} |
| 6 | name: obj | ||||
| 7 | for name, obj in vars(builtins).items() | ||||
| 8 | if isinstance(obj, type) | ||||
| 9 | } | ||||
| 10 | 6 | ||||
| 11 | 7 | ||||
| 12 | def _default_for(type_str): | 8 | def _default_for(type_str): | ||
| 13 | type_str = type_str.strip() | 9 | type_str = type_str.strip() | ||
| 14 | 10 | ||||
| 15 | if '|' in type_str: | 11 | if '|' in type_str: | ||
| 16 | return _default_for(type_str.split('|')[0]) | 12 | return _default_for(type_str.split('|')[0]) | ||
| 17 | 13 | ||||
| 18 | match = re.match(r'^(\w+)\[', type_str) | 14 | match = re.match(r'^(\w+)\[', type_str) | ||
| 19 | type_name = match.group(1) if match else type_str | 15 | type_name = match.group(1) if match else type_str | ||
| 20 | 16 | ||||
| 21 | if type_name == 'None': | 17 | if type_name == 'None': | ||
| 22 | return None | 18 | return None | ||
| 23 | 19 | ||||
| 24 | try: # Code Injection - Try me bitch | 20 | try: # Code Injection - Try me bitch | ||
| 25 | return eval(type_name, {"__builtins__": _SAFE_BUILTINS})() | 21 | return eval(type_name, {"__builtins__": _SAFE_BUILTINS})() | ||
| 26 | except Exception as e: | 22 | except Exception as e: | ||
| 27 | raise AttributeError(f"'{type_name}' is not a valid built-in type") from e | 23 | raise AttributeError(f"'{type_name}' is not a valid built-in type") from e | ||
| 28 | 24 | ||||
| 29 | 25 | ||||
| 30 | def _parse_params(docstring): | 26 | def _parse_params(docstring): | ||
| 31 | if not docstring: | 27 | if not docstring: | ||
| 32 | return [] | 28 | return [] | ||
| 33 | 29 | ||||
| 34 | docstring = inspect.cleandoc(docstring) | 30 | docstring = inspect.cleandoc(docstring) | ||
| 35 | match = re.search(r'Parameters\n----------\n(.*?)(\n\n|$)', docstring, re.DOTALL) | 31 | match = re.search(r'Parameters\n----------\n(.*?)(\n\n|$)', docstring, re.DOTALL) | ||
| 36 | 32 | ||||
| 37 | if not match: | 33 | if not match: | ||
| 38 | return [] | 34 | return [] | ||
| 39 | 35 | ||||
| 40 | variable_block = match.group(1) | 36 | variable_block = match.group(1) | ||
| 41 | # Returns list of the types of all variables | 37 | # Returns list of the types of all variables | ||
| 42 | return re.findall(r'^\w+\s*:\s*(.+)$', variable_block, re.MULTILINE) | 38 | return re.findall(r'^\w+\s*:\s*(.+)$', variable_block, re.MULTILINE) | ||
| 43 | 39 | ||||
| 44 | 40 | ||||
| 45 | class FilteredModule: | 41 | class FilteredModule: | ||
| 46 | def __init__(self, module): | 42 | def __init__(self, module): | ||
| 47 | self.__module = module | 43 | self.__module = module | ||
| 48 | 44 | ||||
| 49 | def __getattr__(self, name): | 45 | def __getattr__(self, name): | ||
| 50 | if not hasattr(self.__module, name): | 46 | if not hasattr(self.__module, name): | ||
| 51 | raise AttributeError(f"Module has no attribute '{name}'") | 47 | raise AttributeError(f"Module has no attribute '{name}'") | ||
| 52 | 48 | ||||
| 53 | obj = getattr(self.__module, name) | 49 | obj = getattr(self.__module, name) | ||
| 54 | 50 | ||||
| 55 | self.__check_name(obj) | 51 | self.__check_name(obj) | ||
| 56 | self.__check_callable(obj) | 52 | self.__check_callable(obj) | ||
| 57 | self.__check_attribute(obj) | 53 | self.__check_attribute(obj) | ||
| 58 | 54 | ||||
| 59 | return obj | 55 | return obj | ||
| 60 | 56 | ||||
| 61 | @staticmethod | 57 | @staticmethod | ||
| 62 | def __check_name(obj): | 58 | def __check_name(obj): | ||
| 63 | if not hasattr(obj, '__name__') or not obj.__name__[0].isupper(): | 59 | if not hasattr(obj, '__name__') or not obj.__name__[0].isupper(): | ||
| 64 | raise AttributeError( | 60 | raise AttributeError( | ||
| 65 | f"'{getattr(obj, '__name__', repr(obj))}' failed question 1: __name__ must start with uppercase") | 61 | f"'{getattr(obj, '__name__', repr(obj))}' failed question 1: __name__ must start with uppercase") | ||
| 66 | 62 | ||||
| 67 | @staticmethod | 63 | @staticmethod | ||
| 68 | def __check_callable(obj): | 64 | def __check_callable(obj): | ||
| 69 | params = _parse_params(obj.__doc__) | 65 | params = _parse_params(obj.__doc__) | ||
| 70 | args = [_default_for(type_str) for type_str in params] | 66 | args = [_default_for(type_str) for type_str in params] | ||
| 71 | 67 | ||||
| 72 | try: | 68 | try: | ||
| 73 | obj(*args) | 69 | obj(*args) | ||
| 74 | except Exception as e: | 70 | except Exception as e: | ||
| 75 | raise AttributeError(f"'{obj.__name__}' failed question 2: call failed or not callable") from e | 71 | raise AttributeError(f"'{obj.__name__}' failed question 2: call failed or not callable") from e | ||
| 76 | 72 | ||||
| 77 | @staticmethod | 73 | @staticmethod | ||
| 78 | def __check_attribute(obj): | 74 | def __check_attribute(obj): | ||
| 79 | for attr in vars(obj).keys(): | 75 | for attr in vars(obj).keys(): | ||
| 80 | # Skip if dunder OR contains 4+ consecutive vowels OR last letter isn't uppercase | 76 | # Skip if dunder OR contains 4+ consecutive vowels OR last letter isn't uppercase | ||
| 81 | if (re.search(r'^__.*__$', attr) or re.search(r'[aeiouAEIOU]{4,}', attr) or | 77 | if (re.search(r'^__.*__$', attr) or re.search(r'[aeiouAEIOU]{4,}', attr) or | ||
| 82 | not re.search(r'[A-Z][^a-zA-Z]*$', attr)): | 78 | not re.search(r'[A-Z][^a-zA-Z]*$', attr)): | ||
| 83 | continue | 79 | continue | ||
| 84 | return | 80 | return | ||
| 85 | 81 | ||||
| 86 | raise AttributeError(f"'{obj.__name__}' failed question 3: no valid attribute found") | 82 | raise AttributeError(f"'{obj.__name__}' failed question 3: no valid attribute found") | ||
| 87 | 83 | ||||
| 88 | 84 | ||||
| 89 | class BridgeKeeper: | 85 | class BridgeKeeper: | ||
| 90 | def __init__(self, module_name): | 86 | def __init__(self, module_name): | ||
| 91 | self.module_name = module_name | 87 | self.module_name = module_name | ||
| 92 | 88 | ||||
| 93 | def __enter__(self): | 89 | def __enter__(self): | ||
| 94 | self.module = __import__(self.module_name) | 90 | self.module = __import__(self.module_name) | ||
| 95 | return FilteredModule(self.module) | 91 | return FilteredModule(self.module) | ||
| 96 | 92 | ||||
| 97 | def __exit__(self, exc_type, exc_val, exc_tb): | 93 | def __exit__(self, exc_type, exc_val, exc_tb): | ||
| 98 | return False | 94 | return False |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||