1import importlib
2import re
3
4
5class FilteredModule:
6
7 def __init__(self, module):
8 self.module = module
9
10 def __getattr__(self, name):
11 obj = getattr(self.module, name)
12
13 if (
14 self.passes_name_question(obj)
15 and self.passes_func_callable(obj)
16 and self.passes_correct_arguments(obj)
17 and self.passes_random_question(obj)
18 ):
19 return obj
20
21 raise AttributeError
22
23 def passes_name_question(self, obj):
24 return hasattr(obj, "__name__") and obj.__name__[0].isupper()
25
26 def passes_func_callable(self, obj):
27 return callable(obj)
28
29 def passes_correct_arguments(self, obj):
30 doc = getattr(obj, "__doc__", None) or ""
31
32 block = self.get_parameters_block(doc)
33
34 if block == "":
35 try:
36 obj()
37 return True
38 except Exception:
39 return False
40
41 parameters = self.parse_parameters(block)
42 args = [self.example_value_for_type(type_text) for _, type_text in parameters]
43
44 try:
45 obj(*args)
46 return True
47 except Exception:
48 return False
49
50 def get_parameters_block(self, doc):
51 pattern = r"Parameters\n----------\n(.*?)(?:\n\n|$)"
52
53 match = re.search(pattern, doc, re.S)
54
55 if match is None:
56 return ""
57
58 return match.group(1).strip()
59
60 def parse_parameters(self, block):
61 parameters = []
62
63 for line in block.splitlines():
64 line = line.strip()
65
66 if " : " not in line:
67 continue
68
69 name, type_text = line.split(" : ", 1)
70
71 parameters.append((name, type_text))
72
73 return parameters
74
75 def example_value_for_type(self, type_text):
76 type_text = type_text.strip()
77
78 if " | " in type_text:
79 first_type = type_text.split(" | ", 1)[0]
80 return self.example_value_for_type(first_type)
81
82 if type_text.startswith("dict[") and type_text.endswith("]"):
83 inner = type_text[5:-1]
84 key_type, value_type = inner.split(",", 1)
85
86 key = self.example_value_for_type(key_type.strip())
87 value = self.example_value_for_type(value_type.strip())
88
89 return {key: value}
90
91 if type_text.startswith("list[") and type_text.endswith("]"):
92 inner_type = type_text[5:-1]
93 return [self.example_value_for_type(inner_type)]
94
95 if type_text.startswith("tuple[") and type_text.endswith("]"):
96 inner_type = type_text[6:-1]
97 return (self.example_value_for_type(inner_type),)
98
99 if type_text.startswith("set[") and type_text.endswith("]"):
100 inner_type = type_text[4:-1]
101 return {self.example_value_for_type(inner_type)}
102
103 examples = {
104 "int": 1,
105 "str": "a",
106 "float": 1.0,
107 "bool": True,
108 }
109
110 return examples[type_text]
111
112 def passes_random_question(self, obj):
113 for attr_name in dir(obj):
114 if self.is_dunder(attr_name):
115 continue
116
117 if self.has_too_many_consecutive_vowels(attr_name):
118 continue
119
120 if not self.last_latin_letter_is_upper(attr_name):
121 continue
122
123 return True
124
125 return False
126
127 def is_dunder(self, attr_name):
128 return attr_name.startswith("__") and attr_name.endswith("__")
129
130 def has_too_many_consecutive_vowels(self, attr_name):
131 vowels = "aeiou"
132 count = 0
133
134 for char in attr_name.lower():
135 if char in vowels:
136 count += 1
137
138 if count > 3:
139 return True
140 else:
141 count = 0
142
143 return False
144
145 def last_latin_letter_is_upper(self, attr_name):
146 for char in reversed(attr_name):
147 if char.isalpha() and char.isascii():
148 return char.isupper()
149
150 return False
151
152
153class BridgeKeeper:
154
155 def __init__(self, module_name):
156 self.module_name = module_name
157
158 def __enter__(self):
159 module = importlib.import_module(self.module_name)
160 return FilteredModule(module)
161
162 def __exit__(self, exc_type, exc_value, traceback):
163 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_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)