1import importlib
2import re
3
4def passes_name(obj):
5 obj_name = getattr(obj, '__name__', None)
6 return bool(obj_name and obj_name[0].isupper())
7
8def parse_params(doc):
9 if not doc:
10 return []
11 match = re.search(r'Parameters\n----------\n(.*?)(\n\n|$)', doc, re.DOTALL)
12 if not match:
13 return []
14 params = []
15 for line in match.group(1).splitlines():
16 line = line.strip()
17 if not line:
18 break #continue? не съм сигурен дали може да има празни редове между параметрите
19 m = re.match(r'^(\w+)\s:\s(.+)$', line)
20 if m:
21 params.append((m.group(1), m.group(2).strip()))
22 return params
23
24def generate_sample_value(type_str):
25 type_str = type_str.strip()
26 if '|' in type_str:
27 return generate_sample_value(type_str.split('|')[0].strip())
28 dict_match = re.match(r'^dict\[(.+),\s(.+)\]$', type_str)
29 if dict_match:
30 key = generate_sample_value(dict_match.group(1).strip())
31 val = generate_sample_value(dict_match.group(2).strip())
32 return {key: val}
33 coll_match = re.match(r'^(list|tuple|set)\[(.+)\]$', type_str)
34 if coll_match:
35 elem = generate_sample_value(coll_match.group(2).strip())
36 return {'list': [elem], 'tuple': (elem,), 'set': {elem}}[coll_match.group(1)]
37 return {'int': 0, 'float': 0.0, 'str': '', 'bool': True, 'complex': 0j}.get(type_str, None)
38
39def passes_quest(obj):
40 if not callable(obj):
41 return False
42 params = parse_params(getattr(obj, '__doc__', None))
43 sample_args = [generate_sample_value(ts) for _, ts in params]
44 try:
45 obj(*sample_args)
46 return True
47 except TypeError:
48 return False
49 except Exception:
50 return True
51
52def is_valid_answer_attr(attr_name):
53 if attr_name.startswith('__') and attr_name.endswith('__'):
54 return False
55 letters_only = re.sub(r'[^a-zA-Z]', '', attr_name)
56 if not letters_only:
57 return False
58 if not letters_only[-1].isupper():
59 return False
60 if re.search(r'[aeiouAEIOU]{4,}', attr_name):
61 return False
62 return True
63
64def passes_colour(obj):
65 return any(is_valid_answer_attr(attr) for attr in dir(obj))
66
67class FilteredModule:
68 def __init__(self, allowed):
69 object.__setattr__(self, '_allowed', allowed)
70
71 def __getattr__(self, name):
72 allowed = object.__getattribute__(self, '_allowed')
73 if name in allowed:
74 return allowed[name]
75 raise AttributeError(f"'{name}' is not accessible through BridgeKeeper")
76
77
78class BridgeKeeper:
79 def __init__(self, module_name):
80 self.module_name = module_name
81
82 def __enter__(self):
83 module = importlib.import_module(self.module_name)
84 allowed = {}
85 for name, obj in vars(module).items():
86 if passes_name(obj) and passes_quest(obj) and passes_colour(obj):
87 allowed[name] = obj
88 return FilteredModule(allowed)
89
90 def __exit__(self, exc_type, exc_val, exc_tb):
91 return False
92
............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.010s
FAILED (failures=2)