1import importlib
2import re
3import itertools
4
5def check_name(name):
6 if not name or not name[0].isupper():
7 raise AttributeError("Name is not correct!")
8
9def check_attribute(attr):
10 if attr.startswith("__") and attr.endswith("__"):
11 return False
12 if re.search(r'[aeouiAEOUI]{4}', attr):
13 return False
14
15 letters = [char for char in attr if char.isalpha()]
16 if letters and letters[-1].isupper():
17 return True
18
19
20def make_args(obj):
21 doc = getattr(obj, "__doc__", "") or ""
22 args = []
23
24
25 match = re.search(r'Parameters\s*\n\s*-+\s*\n(.*?)(?:\n[ \t]*\n|$)', doc, re.DOTALL)
26 if match:
27 for line in match.group(1).splitlines():
28 if ":" in line:
29 type_part = line.split(":", 1)[1].strip()
30 types = []
31 for t_str in type_part.split('|'):
32 t_str = t_str.strip()
33 val = 1
34
35 if "str" in t_str: val = 'a'
36 elif "int" in t_str: val = 1
37 elif "float" in t_str: val = 1.0
38 elif "bool" in t_str: val = True
39
40 if t_str.startswith("list"): val = [val]
41 elif t_str.startswith('tuple'): val = (val,)
42 elif t_str.startswith('set'): val = {val}
43 elif t_str.startswith('dict'): val = {'a': 1}
44
45 types.append(val)
46 args.append(types)
47 return itertools.product(*args)
48
49def check_object(obj):
50 if not callable(obj):
51 return False
52
53 combinations = make_args(obj)
54
55 for arg in combinations:
56 try:
57 obj(*arg)
58 except Exception:
59 raise AttributeError("Object is not correct!")
60
61class BridgeKeeper:
62 def __init__(self, module_name):
63 self.module = importlib.import_module(module_name)
64
65 def __enter__(self):
66 class ModuleProxy:
67 def __getattr__(_, name):
68 try:
69 obj = getattr(self.module, name)
70 except AttributeError:
71 raise AttributeError(f"Module has no attribute '{name}'")
72
73 check_name(getattr(obj, '__name__', ''))
74
75 check_object(obj)
76
77 if not any(check_attribute(attr) for attr in dir(obj)):
78 raise AttributeError("No secret attribute found!")
79
80 return obj
81
82 return ModuleProxy()
83
84 def __exit__(self, exc_type, exc_val, exc_tb):
85 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_non_callable_object (test.TestBridgeKeeper.test_rejects_non_callable_object)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 27, in test_rejects_non_callable_object
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised
----------------------------------------------------------------------
Ran 25 tests in 0.003s
FAILED (failures=2)
Виктор Бечев
01.05.2026 15:13Отвъд горното - кодът изглежда добре. Прост е, използвани са правилните питонски механизми. Може би пропускам някакви дреболии, защото нещо ме човърка като го гледам, но нищо, което да се набива на очи.
|
01.05.2026 15:03
01.05.2026 15:06
01.05.2026 15:11