1import importlib
2import re
3
4
5class BridgeKeeper:
6 def __init__(self, module_name):
7 self.module_name = module_name
8
9 def __enter__(self):
10 module = importlib.import_module(self.module_name)
11 return _FilteredModule(module)
12
13 def __exit__(self, exc_type, exc_val, exc_tb):
14 return False
15
16
17class _FilteredModule:
18 def __init__(self, module):
19 self._module = module
20
21 def __getattr__(self, name):
22 obj = getattr(self._module, name)
23 if (
24 _valid_name(obj)
25 and _valid_callable(obj)
26 and _has_valid_attribute(obj)
27 ):
28 return obj
29 raise AttributeError(f"{name} is not worthy!")
30
31
32def _valid_name(obj):
33 name = getattr(obj, "__name__", None)
34 return isinstance(name, str) and name and name[0].isupper()
35
36def _valid_callable(obj):
37 if not callable(obj):
38 return False
39 params = _parse_docstring(obj)
40 try:
41 args = [_dummy_value(t) for t in params]
42 obj(*args)
43 return True
44 except Exception:
45 return False
46
47def _parse_docstring(obj):
48 doc = obj.__doc__
49 if not doc:
50 return []
51 match = re.search(
52 r"Parameters\s*-+\s*(.*?)(\n\s*\n|$)",
53 doc,
54 re.DOTALL,
55 )
56 if not match:
57 return []
58 block = match.group(1).strip().splitlines()
59 result = []
60 for line in block:
61 line = line.strip()
62 if not line or ":" not in line:
63 continue
64 _, type_part = line.split(":", 1)
65 result.append(type_part.strip())
66 return result
67
68def _dummy_value(type_str):
69 if "|" in type_str:
70 type_str = type_str.split("|")[0].strip()
71 collection_match = re.match(r"(list|tuple|set)\[(.+)\]", type_str)
72 if collection_match:
73 collection, inner = collection_match.groups()
74 val = _dummy_value(inner)
75 if collection == "list":
76 return [val]
77 elif collection == "tuple":
78 return (val,)
79 elif collection == "set":
80 return {val}
81 dict_match = re.match(r"dict\[(.+),(.+)\]", type_str)
82 if dict_match:
83 k, v = dict_match.groups()
84 return {_dummy_value(k.strip()): _dummy_value(v.strip())}
85 mapping = {
86 "int": 1,
87 "float": 1.0,
88 "str": "a",
89 "bool": True,
90 }
91 return mapping.get(type_str, None)
92
93def _has_valid_attribute(obj):
94 for attr in dir(obj):
95 if attr.startswith("__") and attr.endswith("__"):
96 continue
97 if _valid_attribute_name(attr):
98 return True
99 return False
100
101def _valid_attribute_name(name):
102 if re.search(r"[aeiouAEIOU]{4,}", name):
103 return False
104 letters = [c for c in name if c.isalpha()]
105 if not letters:
106 return False
107 if not letters[-1].isupper():
108 return False
109 return True
............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)