1import importlib
2
3class BridgeKeeper:
4 def __init__(self, module_name):
5 self.module_name = module_name
6 self.module = None
7
8 def __enter__(self):
9 self.module = importlib.import_module(self.module_name)
10 return FilteredModule(self.module)
11
12 def __exit__(self, exc_type, exc_val, exc_tb):
13 pass
14
15
16class FilteredModule:
17 def __init__(self, module):
18 self._module = module
19
20 def __getattr__(self, attr_name):
21 obj = getattr(self._module, attr_name)
22
23 if is_allowed(obj):
24 return obj
25
26 raise AttributeError(f"{attr_name} not allowed")
27
28def is_allowed(obj):
29 return ( answers_name_question(obj)
30 and answers_quest_question(obj)
31 and answers_random_question(obj))
32
33def answers_name_question(obj):
34 if not hasattr(obj, "__name__"):
35 return False
36
37 name = obj.__name__
38
39 if not name:
40 return False
41
42 return name[0].isupper()
43
44def answers_quest_question(obj):
45 if not callable(obj):
46 return False
47
48 try:
49 all_args = build_args_from_docstring(obj.__doc__)
50
51 for args in all_args:
52 try:
53 obj(*args)
54 return True
55 except Exception:
56 continue
57
58 return False
59 except Exception:
60 return False
61
62def build_args_from_docstring(docstring):
63 block = extract_parameters_block(docstring)
64
65 if not block:
66 return [[]]
67
68 options = []
69
70 for line in block.splitlines():
71 line = line.strip()
72
73 if not line:
74 continue
75
76 if ":" not in line:
77 continue
78
79 name, type_expr = line.split(":", 1)
80 options.append(example_values(type_expr.strip()))
81
82 result = [[]]
83
84 for values in options:
85 new_result = []
86
87 for current_args in result:
88 for value in values:
89 new_result.append(current_args + [value])
90
91 result = new_result
92
93 return result
94
95def extract_parameters_block(docstring):
96 if not docstring:
97 return ""
98
99 lines = docstring.splitlines()
100
101 start = None
102
103 for i in range(len(lines) - 1):
104 current_line = lines[i].strip()
105 next_line = lines[i + 1].strip()
106
107 if current_line == "Parameters" and is_dash_line(next_line):
108 start = i + 2
109 break
110
111 if start is None: return ""
112
113 block_lines = []
114
115 for i in range (start, len(lines)):
116 line = lines[i]
117
118 if line.strip() == "": break
119
120 block_lines.append(line)
121
122 return "\n".join(block_lines)
123
124def is_dash_line(line):
125 if not line: return False
126
127 for char in line:
128 if char != "-":
129 return False
130
131 return True
132
133def example_value(type_expr):
134 type_expr = type_expr.strip()
135
136 if type_expr.startswith("list[") and type_expr.endswith("]"):
137 inner_type = type_expr[5:-1]
138 return [example_value(inner_type)]
139
140 if type_expr.startswith("tuple[") and type_expr.endswith("]"):
141 inner_type = type_expr[6:-1]
142 return (example_value(inner_type),)
143
144 if type_expr.startswith("set[") and type_expr.endswith("]"):
145 inner_type = type_expr[4:-1]
146 return {example_value(inner_type)}
147
148 if type_expr.startswith("dict[") and type_expr.endswith("]"):
149 inner = type_expr[5:-1]
150 key_type, value_type = split_dict_types(inner)
151
152 return {
153 example_value(key_type): example_value(value_type)
154 }
155
156 return example_basic_value(type_expr)
157
158def example_values(type_expr):
159 type_expr = type_expr.strip()
160
161 if "|" in type_expr:
162 values = []
163
164 for part in type_expr.split("|"):
165 values.extend(example_values(part.strip()))
166
167 return values
168
169 return [example_value(type_expr)]
170
171def split_dict_types(inner):
172 key_type, value_type = inner.split(",", 1)
173 return key_type.strip(), value_type.strip()
174
175def example_basic_value(type_name):
176 values = {
177 "int": 1,
178 "float": 1.0,
179 "str": "a",
180 "bool": True,
181 "complex": 1 + 1j,
182 "bytes": b"a",
183 }
184
185 return values[type_name]
186
187def answers_random_question(obj):
188 for attr_name in dir(obj):
189 if is_valid_secret_attribute(attr_name):
190 return True
191
192 return False
193
194def is_valid_secret_attribute(name):
195 if is_dunder(name):
196 return False
197
198 if has_more_than_3_consecutive_vowels(name):
199 return False
200
201 last_latin_letter = get_last_latin_letter(name)
202
203 if last_latin_letter is None:
204 return False
205
206 return last_latin_letter.isupper()
207
208
209def is_dunder(name):
210 return name.startswith("__") and name.endswith("__")
211
212def has_more_than_3_consecutive_vowels(name):
213 vowels = "aeiouAEIOU"
214 counter = 0
215
216 for char in name:
217 if char in vowels:
218 counter += 1
219
220 if counter > 3:
221 return True
222
223 else:
224 counter = 0
225
226 return False
227
228def get_last_latin_letter(name):
229 for char in reversed(name):
230 if ("a" <= char <= "z") or ("A" <= char <= "Z"):
231 return char
232
233 return None
............F...........F
======================================================================
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_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=2)