1import importlib
2import itertools
3import re
4
5
6def _check_name(obj):
7 try:
8 name = getattr(obj, '__name__', None)
9 if isinstance(name, str) and name and name[0].isupper():
10 return True
11 except Exception:
12 pass
13 return False
14
15
16def _generate_dummies(type_str):
17 results = []
18 for t in type_str.split('|'):
19 t = t.strip()
20 if t == 'int':
21 results.append(1)
22 elif t == 'float':
23 results.append(1.0)
24 elif t == 'str':
25 results.append('a')
26 elif t == 'bool':
27 results.append(True)
28 elif match := re.match(r'list\[(.+)\]', t):
29 results.extend([[sub] for sub in _generate_dummies(match.group(1))])
30 elif match := re.match(r'tuple\[(.+)\]', t):
31 results.extend([(sub,) for sub in _generate_dummies(match.group(1))])
32 elif match := re.match(r'set\[(.+)\]', t):
33 for sub in _generate_dummies(match.group(1)):
34 try:
35 results.append({sub})
36 except TypeError:
37 pass
38 elif match := re.match(r'dict\[(.+?),\s*(.+)\]', t):
39 k_types = _generate_dummies(match.group(1))
40 v_types = _generate_dummies(match.group(2))
41 for k in k_types:
42 for v in v_types:
43 try:
44 results.append({k: v})
45 except TypeError:
46 pass
47 return results
48
49
50def _check_quest(obj):
51 if not callable(obj):
52 return False
53
54 try:
55 doc = getattr(obj, '__doc__', None)
56 except Exception:
57 return False
58
59 param_types = []
60 if doc:
61 match = re.search(r'Parameters\r?\n-+\r?\n(.*?)(?:\r?\n\r?\n|\Z)', doc, re.DOTALL)
62 if match:
63 block = match.group(1)
64 for line in block.strip().split('\n'):
65 line = line.strip()
66 if ':' in line:
67 _, p_type = line.split(':', 1)
68 param_types.append(p_type.strip())
69
70 if not param_types:
71 try:
72 obj()
73 return True
74 except Exception:
75 return False
76
77 try:
78 param_options = [_generate_dummies(pt) for pt in param_types]
79 for args in itertools.product(*param_options):
80 obj(*args)
81 return True
82 except Exception:
83 return False
84
85
86def _is_valid_secret_attr(attr):
87 if attr.startswith('__') and attr.endswith('__'):
88 return False
89
90 if re.search(r'[aeiouAEIOU]{4,}', attr):
91 return False
92
93 letters = re.findall(r'[a-zA-Z]', attr)
94 return bool(letters and letters[-1].isupper())
95
96
97def _check_secret_attr(obj):
98 try:
99 attrs = dir(obj)
100 except Exception:
101 return False
102
103 return any(_is_valid_secret_attr(attr) for attr in attrs)
104
105
106class FilteredModuleProxy:
107 def __init__(self, module):
108 self._module = module
109
110 def __getattr__(self, name):
111 try:
112 obj = getattr(self._module, name)
113 except AttributeError:
114 raise AttributeError(f"Module has no attribute '{name}'")
115
116 if not (_check_name(obj) and _check_quest(obj) and _check_secret_attr(obj)):
117 raise AttributeError(f"Attribute '{name}' failed the BridgeKeeper's questions.")
118
119 return obj
120
121
122class BridgeKeeper:
123 def __init__(self, module_name):
124 self.module_name = module_name
125
126 def __enter__(self):
127 module = importlib.import_module(self.module_name)
128 return FilteredModuleProxy(module)
129
130 def __exit__(self, exc_type, exc_val, exc_tb):
131 pass
............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
----------------------------------------------------------------------
Ran 25 tests in 0.002s
FAILED (failures=1)