1import importlib
2import inspect
3import re
4from itertools import product
5
6
7class BridgeKeeper:
8 def __init__(self, module_name):
9 self.module_name = module_name
10 self.module = None
11 self.allowed_objects = {}
12
13 def __enter__(self):
14 self.module = importlib.import_module(self.module_name)
15
16 for name in dir(self.module):
17 obj = getattr(self.module, name)
18
19 if self._answers_all_questions(obj):
20 self.allowed_objects[name] = obj
21
22 return _FilteredModule(self.allowed_objects)
23
24 def __exit__(self, exc_type, exc_value, traceback):
25 return False
26
27 def _answers_all_questions(self, obj):
28 return (
29 self._has_good_name(obj)
30 and self._has_good_quest(obj)
31 and self._has_secret_answer(obj)
32 )
33
34 def _has_good_name(self, obj):
35 if not hasattr(obj, "__name__"):
36 return False
37
38 name = obj.__name__
39
40 if not isinstance(name, str) or name == "":
41 return False
42
43 return name[0].isupper()
44
45 def _has_good_quest(self, obj):
46 if not callable(obj):
47 return False
48
49 parameters = self._get_parameters_from_docstring(obj)
50
51 try:
52 signature = inspect.signature(obj)
53 real_params = [
54 param.name
55 for param in signature.parameters.values()
56 if param.kind in (
57 param.POSITIONAL_ONLY,
58 param.POSITIONAL_OR_KEYWORD,
59 param.KEYWORD_ONLY,
60 )
61 ]
62
63 doc_param_names = [name for name, _ in parameters]
64
65 if real_params != doc_param_names:
66 return False
67 except (TypeError, ValueError):
68 pass
69
70 possible_arguments = []
71
72 for _, type_text in parameters:
73 values = self._values_for_type(type_text)
74 possible_arguments.append(values)
75
76 try:
77 for args in product(*possible_arguments):
78 obj(*args)
79 except Exception:
80 return False
81
82 return True
83
84 def _get_parameters_from_docstring(self, obj):
85 docstring = inspect.getdoc(obj)
86
87 if not docstring:
88 return []
89
90 match = re.search(
91 r"^Parameters\s*\n-+\s*\n(.*?)(?:\n\s*\n|\Z)",
92 docstring,
93 re.MULTILINE | re.DOTALL,
94 )
95
96 if not match:
97 return []
98
99 block = match.group(1)
100 parameters = []
101
102 for line in block.splitlines():
103 param_match = re.match(r"\s*([a-zA-Z_]\w*)\s*:\s*(.+)\s*$", line)
104
105 if param_match:
106 name = param_match.group(1)
107 type_text = param_match.group(2)
108 parameters.append((name, type_text))
109
110 return parameters
111
112 def _values_for_type(self, type_text):
113 parts = self._split_union(type_text)
114
115 values = []
116
117 for part in parts:
118 values.append(self._value_for_single_type(part.strip()))
119
120 return values
121
122 def _split_union(self, type_text):
123 parts = []
124 current = ""
125 brackets = 0
126
127 for char in type_text:
128 if char == "[":
129 brackets += 1
130 elif char == "]":
131 brackets -= 1
132
133 if char == "|" and brackets == 0:
134 parts.append(current.strip())
135 current = ""
136 else:
137 current += char
138
139 parts.append(current.strip())
140 return parts
141
142 def _value_for_single_type(self, type_text):
143 base_values = {
144 "int": 1,
145 "float": 1.5,
146 "str": "text",
147 "bool": True,
148 "complex": 1 + 2j,
149 "bytes": b"text",
150 }
151
152 if type_text in base_values:
153 return base_values[type_text]
154
155 list_match = re.match(r"list\[(.+)\]$", type_text)
156 if list_match:
157 inner_type = list_match.group(1).strip()
158 return [self._value_for_single_type(inner_type)]
159
160 tuple_match = re.match(r"tuple\[(.+)\]$", type_text)
161 if tuple_match:
162 inner_type = tuple_match.group(1).strip()
163 return (self._value_for_single_type(inner_type),)
164
165 set_match = re.match(r"set\[(.+)\]$", type_text)
166 if set_match:
167 inner_type = set_match.group(1).strip()
168 return {self._value_for_single_type(inner_type)}
169
170 dict_match = re.match(r"dict\[(.+),\s*(.+)\]$", type_text)
171 if dict_match:
172 key_type = dict_match.group(1).strip()
173 value_type = dict_match.group(2).strip()
174
175 key = self._value_for_single_type(key_type)
176 value = self._value_for_single_type(value_type)
177
178 return {key: value}
179
180 return None
181
182 def _has_secret_answer(self, obj):
183 for attribute_name in dir(obj):
184 if self._is_valid_secret_attribute(attribute_name):
185 return True
186
187 return False
188
189 def _is_valid_secret_attribute(self, name):
190 if name.startswith("__") and name.endswith("__"):
191 return False
192
193 if re.search(r"[aeiouAEIOU]{4,}", name):
194 return False
195
196 letters = re.findall(r"[a-zA-Z]", name)
197
198 if not letters:
199 return False
200
201 return letters[-1].isupper()
202
203
204class _FilteredModule:
205 def __init__(self, allowed_objects):
206 self.allowed_objects = allowed_objects
207
208 def __getattr__(self, name):
209 if name in self.allowed_objects:
210 return self.allowed_objects[name]
211
212 raise AttributeError(f"Object '{name}' is not allowed by the BridgeKeeper")
.........................
----------------------------------------------------------------------
Ran 25 tests in 0.026s
OK
30.04.2026 17:11
30.04.2026 17:14