1import importlib
2import inspect
3import re
4import itertools
5
6
7def _valid_name(obj):
8 name = getattr(obj, "__name__", None)
9 return isinstance(name, str) and name and name[0].isupper()
10
11
12def _valid_attr(obj):
13 for attr in dir(obj):
14 if attr.startswith("__") and attr.endswith("__"):
15 continue
16 if re.search(r"[aeiouAEIOU]{4,}", attr):
17 continue
18 letters = [ch for ch in attr if ch.isalpha()]
19 if not letters:
20 continue
21 if letters[-1].isupper():
22 return True
23 return False
24
25
26def _parse_doc_params(obj):
27 doc = inspect.getdoc(obj)
28 if not doc:
29 return []
30
31 match = re.search(r"Parameters\n----------\n(.*?)(\n\s*\n|\Z)", doc, re.DOTALL)
32 if not match:
33 return []
34
35 params = []
36 for line in match.group(1).splitlines():
37 line = line.strip()
38 m = re.match(r"^([A-Za-z_]\w*)\s*:\s*(.+)$", line)
39 if m:
40 params.append((m.group(1), m.group(2).strip()))
41 return params
42
43
44def _sample_for_type(t):
45 t = t.strip()
46
47 if "|" in t:
48 parts = t.split("|")
49 res = []
50 for p in parts:
51 res += _sample_for_type(p.strip())
52 return res
53
54 if t.startswith("list["):
55 inner = t[5:-1]
56 return [[_sample_for_type(inner)[0]]]
57
58 if t.startswith("tuple["):
59 inner = t[6:-1]
60 return [(_sample_for_type(inner)[0],)]
61
62 if t.startswith("set["):
63 inner = t[4:-1]
64 try:
65 return [{_sample_for_type(inner)[0]}]
66 except:
67 return []
68
69 if t.startswith("dict["):
70 inner = t[5:-1]
71 k, v = inner.split(",")
72 return [{_sample_for_type(k.strip())[0]: _sample_for_type(v.strip())[0]}]
73
74 basics = {
75 "int": 7,
76 "float": 7.5,
77 "str": "Z",
78 "bool": True,
79 "bytes": b"Z",
80 "complex": 7 + 0j,
81 "None": None,
82 "NoneType": None,
83 }
84
85 if t in basics:
86 return [basics[t]]
87
88 return []
89
90
91def _callable_works(obj):
92 params = _parse_doc_params(obj)
93
94 if not params:
95 try:
96 obj()
97 return True
98 except Exception:
99 return False
100
101 try:
102 sig = inspect.signature(obj)
103 except Exception:
104 return False
105
106 sig_params = list(sig.parameters.values())
107 if len(sig_params) != len(params):
108 return False
109
110 for sp, (doc_name, _) in zip(sig_params, params):
111 if sp.name != doc_name:
112 return False
113
114 all_samples = []
115 for _, type_str in params:
116 samples = _sample_for_type(type_str)
117 if not samples:
118 return False
119 all_samples.append(samples)
120
121 for combo in itertools.product(*all_samples):
122 try:
123 obj(*combo)
124 except Exception:
125 return False
126
127 return True
128
129
130def _qualifies(obj):
131 return (
132 _valid_name(obj) and callable(obj) and _valid_attr(obj) and _callable_works(obj)
133 )
134
135
136class _FilteredModule:
137 def __init__(self, module):
138 self._module = module
139
140 def __getattr__(self, name):
141 try:
142 obj = getattr(self._module, name)
143 except AttributeError:
144 raise AttributeError(name)
145
146 if _qualifies(obj):
147 return obj
148 raise AttributeError(name)
149
150
151class BridgeKeeper:
152 def __init__(self, module_name):
153 self._module_name = module_name
154
155 def __enter__(self):
156 module = importlib.import_module(self._module_name)
157 return _FilteredModule(module)
158
159 def __exit__(self, exc_type, exc, tb):
160 return False
.........................
----------------------------------------------------------------------
Ran 25 tests in 0.002s
OK
Виктор Бечев
30.04.2026 14:07На пръв поглед решението изглежда по-сложно, отколкото е, но всъщност се чете лесно. Дали може по-добре - със сигурност, например `_sample_for_type` няма да пострада от рефакториране, но предвид дизайн решенията, които си взел - доста прилично.
|
30.04.2026 14:01
30.04.2026 14:05