1import importlib
2import re
3from itertools import product
4
5class BridgeKeeperProxy:
6 def __init__(self, module):
7 self._module = module
8
9 def __getattr__(self, name):
10 try:
11 obj = getattr(self._module, name)
12 except AttributeError:
13 raise AttributeError(f"The module has no attribute named'{name}'")
14
15 if self._ask_question_1(obj) and self._ask_question_2(obj) and self._ask_question_3(obj):
16 return obj
17
18 raise AttributeError(f"The object '{name}' did not pass the Bridge Guardian's questions!")
19
20 def _generate_dummy_arg(self, type_str):
21 options = [t.strip() for t in type_str.split('|')]
22
23 for opt in options:
24 val = self._generate_single_type(opt)
25 if val is not None:
26 return val
27 return None
28
29 def _extract_params(self, doc):
30 if not doc:
31 return []
32
33 lines = doc.splitlines()
34 start = None
35
36 for i, line in enumerate(lines):
37 if line.strip() == "Parameters":
38 start = i + 2
39 break
40
41 if start is None:
42 return []
43
44 params = []
45 for line in lines[start:]:
46 if not line.strip():
47 break
48 if ':' in line:
49 _, val = line.split(':', 1)
50 params.append(val.strip())
51
52 return params
53
54 def _generate_single_type(self, type_str):
55 if type_str == 'int': return 1
56 if type_str == 'float': return 1.5
57 if type_str == 'str': return "str"
58 if type_str == 'bool': return True
59
60 dict_match = re.search(r'dict\[(.+?),\s*(.+?)\]', type_str)
61 if dict_match:
62 k = self._generate_dummy_arg(dict_match.group(1))
63 v = self._generate_dummy_arg(dict_match.group(2))
64 return {k: v}
65
66 coll_match = re.search(r'(list|tuple|set)\[(.+?)\]', type_str)
67 if coll_match:
68 item = self._generate_dummy_arg(coll_match.group(2))
69 if coll_match.group(1) == 'list': return [item]
70 if coll_match.group(1) == 'tuple': return (item,)
71 if coll_match.group(1) == 'set': return {item}
72
73 return None
74
75 def _ask_question_1(self, obj):
76 name = getattr(obj, '__name__', '')
77 return isinstance(name, str) and len(name) > 0 and name[0].isupper()
78
79 def _ask_question_2(self, obj):
80 if not callable(obj):
81 return False
82
83 doc = getattr(obj, '__doc__', '') or ''
84 types = self._extract_params(doc)
85 all_options = []
86 for t in types:
87 variants = [opt.strip() for opt in t.split('|')]
88 generated = [self._generate_single_type(v) for v in variants]
89 all_options.append(generated)
90
91 for combo in product(*all_options):
92 try:
93 obj(*combo)
94 return True
95 except Exception:
96 continue
97
98 return False
99
100 def _ask_question_3(self, obj):
101 for attr in dir(obj):
102 if (attr.startswith('__') and attr.endswith('__')) or (re.search(r'[aeiouAEIOU]{4,}', attr)):
103 continue
104
105 latin_letters = [l for l in attr if l in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ']
106 if latin_letters and latin_letters[-1].isupper():
107 return True
108 return False
109
110
111class BridgeKeeper:
112 def __init__(self, module_name):
113 self.module_name = module_name
114 self.proxy = None
115
116 def __enter__(self):
117 module = importlib.import_module(self.module_name)
118 self.proxy = BridgeKeeperProxy(module)
119 return self.proxy
120
121 def __exit__(self, exc_type, exc_val, exc_tb):
122 pass
............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.002s
FAILED (failures=2)
Виктор Бечев
27.04.2026 18:11Генерално добре решение. Тук таме има още някакви дребни неща, но съм достатъчно 50:50 дали всъщност не са окей, че да не ги спомена.
|
| f | 1 | import importlib | f | 1 | import importlib |
| 2 | import re | 2 | import re | ||
| 3 | from itertools import product | 3 | from itertools import product | ||
| 4 | 4 | ||||
| 5 | class BridgeKeeperProxy: | 5 | class BridgeKeeperProxy: | ||
| 6 | def __init__(self, module): | 6 | def __init__(self, module): | ||
| 7 | self._module = module | 7 | self._module = module | ||
| 8 | 8 | ||||
| 9 | def __getattr__(self, name): | 9 | def __getattr__(self, name): | ||
| 10 | try: | 10 | try: | ||
| 11 | obj = getattr(self._module, name) | 11 | obj = getattr(self._module, name) | ||
| 12 | except AttributeError: | 12 | except AttributeError: | ||
| n | 13 | raise AttributeError(f"Модулът няма атрибут с име '{name}'") | n | 13 | raise AttributeError(f"The module has no attribute named'{name}'") |
| 14 | 14 | ||||
| 15 | if self._ask_question_1(obj) and self._ask_question_2(obj) and self._ask_question_3(obj): | 15 | if self._ask_question_1(obj) and self._ask_question_2(obj) and self._ask_question_3(obj): | ||
| 16 | return obj | 16 | return obj | ||
| 17 | 17 | ||||
| t | 18 | raise AttributeError(f"Обектът '{name}' не премина въпросите на Пазителя на моста!") | t | 18 | raise AttributeError(f"The object '{name}' did not pass the Bridge Guardian's questions!") |
| 19 | 19 | ||||
| 20 | def _generate_dummy_arg(self, type_str): | 20 | def _generate_dummy_arg(self, type_str): | ||
| 21 | options = [t.strip() for t in type_str.split('|')] | 21 | options = [t.strip() for t in type_str.split('|')] | ||
| 22 | 22 | ||||
| 23 | for opt in options: | 23 | for opt in options: | ||
| 24 | val = self._generate_single_type(opt) | 24 | val = self._generate_single_type(opt) | ||
| 25 | if val is not None: | 25 | if val is not None: | ||
| 26 | return val | 26 | return val | ||
| 27 | return None | 27 | return None | ||
| 28 | 28 | ||||
| 29 | def _extract_params(self, doc): | 29 | def _extract_params(self, doc): | ||
| 30 | if not doc: | 30 | if not doc: | ||
| 31 | return [] | 31 | return [] | ||
| 32 | 32 | ||||
| 33 | lines = doc.splitlines() | 33 | lines = doc.splitlines() | ||
| 34 | start = None | 34 | start = None | ||
| 35 | 35 | ||||
| 36 | for i, line in enumerate(lines): | 36 | for i, line in enumerate(lines): | ||
| 37 | if line.strip() == "Parameters": | 37 | if line.strip() == "Parameters": | ||
| 38 | start = i + 2 | 38 | start = i + 2 | ||
| 39 | break | 39 | break | ||
| 40 | 40 | ||||
| 41 | if start is None: | 41 | if start is None: | ||
| 42 | return [] | 42 | return [] | ||
| 43 | 43 | ||||
| 44 | params = [] | 44 | params = [] | ||
| 45 | for line in lines[start:]: | 45 | for line in lines[start:]: | ||
| 46 | if not line.strip(): | 46 | if not line.strip(): | ||
| 47 | break | 47 | break | ||
| 48 | if ':' in line: | 48 | if ':' in line: | ||
| 49 | _, val = line.split(':', 1) | 49 | _, val = line.split(':', 1) | ||
| 50 | params.append(val.strip()) | 50 | params.append(val.strip()) | ||
| 51 | 51 | ||||
| 52 | return params | 52 | return params | ||
| 53 | 53 | ||||
| 54 | def _generate_single_type(self, type_str): | 54 | def _generate_single_type(self, type_str): | ||
| 55 | if type_str == 'int': return 1 | 55 | if type_str == 'int': return 1 | ||
| 56 | if type_str == 'float': return 1.5 | 56 | if type_str == 'float': return 1.5 | ||
| 57 | if type_str == 'str': return "str" | 57 | if type_str == 'str': return "str" | ||
| 58 | if type_str == 'bool': return True | 58 | if type_str == 'bool': return True | ||
| 59 | 59 | ||||
| 60 | dict_match = re.search(r'dict\[(.+?),\s*(.+?)\]', type_str) | 60 | dict_match = re.search(r'dict\[(.+?),\s*(.+?)\]', type_str) | ||
| 61 | if dict_match: | 61 | if dict_match: | ||
| 62 | k = self._generate_dummy_arg(dict_match.group(1)) | 62 | k = self._generate_dummy_arg(dict_match.group(1)) | ||
| 63 | v = self._generate_dummy_arg(dict_match.group(2)) | 63 | v = self._generate_dummy_arg(dict_match.group(2)) | ||
| 64 | return {k: v} | 64 | return {k: v} | ||
| 65 | 65 | ||||
| 66 | coll_match = re.search(r'(list|tuple|set)\[(.+?)\]', type_str) | 66 | coll_match = re.search(r'(list|tuple|set)\[(.+?)\]', type_str) | ||
| 67 | if coll_match: | 67 | if coll_match: | ||
| 68 | item = self._generate_dummy_arg(coll_match.group(2)) | 68 | item = self._generate_dummy_arg(coll_match.group(2)) | ||
| 69 | if coll_match.group(1) == 'list': return [item] | 69 | if coll_match.group(1) == 'list': return [item] | ||
| 70 | if coll_match.group(1) == 'tuple': return (item,) | 70 | if coll_match.group(1) == 'tuple': return (item,) | ||
| 71 | if coll_match.group(1) == 'set': return {item} | 71 | if coll_match.group(1) == 'set': return {item} | ||
| 72 | 72 | ||||
| 73 | return None | 73 | return None | ||
| 74 | 74 | ||||
| 75 | def _ask_question_1(self, obj): | 75 | def _ask_question_1(self, obj): | ||
| 76 | name = getattr(obj, '__name__', '') | 76 | name = getattr(obj, '__name__', '') | ||
| 77 | return isinstance(name, str) and len(name) > 0 and name[0].isupper() | 77 | return isinstance(name, str) and len(name) > 0 and name[0].isupper() | ||
| 78 | 78 | ||||
| 79 | def _ask_question_2(self, obj): | 79 | def _ask_question_2(self, obj): | ||
| 80 | if not callable(obj): | 80 | if not callable(obj): | ||
| 81 | return False | 81 | return False | ||
| 82 | 82 | ||||
| 83 | doc = getattr(obj, '__doc__', '') or '' | 83 | doc = getattr(obj, '__doc__', '') or '' | ||
| 84 | types = self._extract_params(doc) | 84 | types = self._extract_params(doc) | ||
| 85 | all_options = [] | 85 | all_options = [] | ||
| 86 | for t in types: | 86 | for t in types: | ||
| 87 | variants = [opt.strip() for opt in t.split('|')] | 87 | variants = [opt.strip() for opt in t.split('|')] | ||
| 88 | generated = [self._generate_single_type(v) for v in variants] | 88 | generated = [self._generate_single_type(v) for v in variants] | ||
| 89 | all_options.append(generated) | 89 | all_options.append(generated) | ||
| 90 | 90 | ||||
| 91 | for combo in product(*all_options): | 91 | for combo in product(*all_options): | ||
| 92 | try: | 92 | try: | ||
| 93 | obj(*combo) | 93 | obj(*combo) | ||
| 94 | return True | 94 | return True | ||
| 95 | except Exception: | 95 | except Exception: | ||
| 96 | continue | 96 | continue | ||
| 97 | 97 | ||||
| 98 | return False | 98 | return False | ||
| 99 | 99 | ||||
| 100 | def _ask_question_3(self, obj): | 100 | def _ask_question_3(self, obj): | ||
| 101 | for attr in dir(obj): | 101 | for attr in dir(obj): | ||
| 102 | if (attr.startswith('__') and attr.endswith('__')) or (re.search(r'[aeiouAEIOU]{4,}', attr)): | 102 | if (attr.startswith('__') and attr.endswith('__')) or (re.search(r'[aeiouAEIOU]{4,}', attr)): | ||
| 103 | continue | 103 | continue | ||
| 104 | 104 | ||||
| 105 | latin_letters = [l for l in attr if l in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'] | 105 | latin_letters = [l for l in attr if l in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'] | ||
| 106 | if latin_letters and latin_letters[-1].isupper(): | 106 | if latin_letters and latin_letters[-1].isupper(): | ||
| 107 | return True | 107 | return True | ||
| 108 | return False | 108 | return False | ||
| 109 | 109 | ||||
| 110 | 110 | ||||
| 111 | class BridgeKeeper: | 111 | class BridgeKeeper: | ||
| 112 | def __init__(self, module_name): | 112 | def __init__(self, module_name): | ||
| 113 | self.module_name = module_name | 113 | self.module_name = module_name | ||
| 114 | self.proxy = None | 114 | self.proxy = None | ||
| 115 | 115 | ||||
| 116 | def __enter__(self): | 116 | def __enter__(self): | ||
| 117 | module = importlib.import_module(self.module_name) | 117 | module = importlib.import_module(self.module_name) | ||
| 118 | self.proxy = BridgeKeeperProxy(module) | 118 | self.proxy = BridgeKeeperProxy(module) | ||
| 119 | return self.proxy | 119 | return self.proxy | ||
| 120 | 120 | ||||
| 121 | def __exit__(self, exc_type, exc_val, exc_tb): | 121 | def __exit__(self, exc_type, exc_val, exc_tb): | ||
| 122 | pass | 122 | pass | ||
| 123 | 123 | ||||
| 124 | 124 | ||||
| 125 | 125 | ||||
| 126 | 126 | ||||
| 127 | 127 |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
27.04.2026 18:04
27.04.2026 18:07