Домашни > The Old Man from Scene #24 > Решения > Решението на Нора Кирилова

Резултати
4 точки от тестове
1 точки от учител

5 точки общо

23 успешни теста
2 неуспешни теста
Код
Скрий всички коментари

  1import importlib
  2import re
  3
  4class BridgeKeeper:
  5    def __init__(self, module_name):
  6        self.module_name = module_name
  7        self.module = None
  8
  9    def __enter__(self):
 10        self.module = importlib.import_module(self.module_name)
 11        return _FilteredModule(self.module)
 12
 13    def __exit__(self, exc_type, exc_value, traceback):
 14        return False
 15
 16class _FilteredModule:
 17    def __init__(self, module):
 18        self._module = module
 19
 20    def __getattr__(self, name):
 21        obj = getattr(self._module, name)
 22
 23        if _answers_all_questions(obj):
 24            return obj
 25        raise AttributeError(f"'{name}' is not allowed by the BridgeKeeper")
 26
 27def _answers_all_questions(obj):
 28    return (
 29        _has_valid_name(obj)
 30        and _has_valid_quest(obj)
 31        and _has_valid_secret_attribute(obj)
 32    )
 33
 34def _has_valid_name(obj):
 35    name = getattr(obj, "__name__", None)
 36    return isinstance(name, str) and len(name) > 0 and name[0].isupper()
 37
 38def _has_valid_quest(obj):
 39    if not callable(obj):
 40        return False
 41
 42    params = _extract_parameters(obj)
 43
 44    try:
 45        args = [_sample_value(param_type) for _, param_type in params]
 46        obj(*args)
 47        return True
 48    except Exception:
 49        return False
 50
 51def _extract_parameters(obj):
 52    doc = getattr(obj, "__doc__", None)
 53
 54    if not doc:
 55        return []
 56
 57    match = re.search(
 58        r"Parameters\s*\n----------\s*\n(.*?)(?:\n\s*\n|$)",
 59        doc,
 60        re.DOTALL,
 61    )
 62
 63    if not match:
 64        return []
 65
 66    block = match.group(1)
 67    params = []
 68
 69    for line in block.splitlines():
 70        line = line.strip()
 71
 72        if not line or ":" not in line:
 73            continue
 74
 75        name, type_expr = line.split(":", 1)
 76        params.append((name.strip(), type_expr.strip()))
 77
 78    return params
 79
 80def _sample_value(type_expr):
 81    type_expr = type_expr.strip()
 82
 83    if "|" in type_expr:
 84        first_type = type_expr.split("|", 1)[0].strip()
 85        return _sample_value(first_type)
 86
 87    collection_match = re.fullmatch(r"(list|tuple|set)\[(.+)\]", type_expr)
 88
 89    if collection_match:
 90        collection_type = collection_match.group(1)
 91        inner_type = collection_match.group(2).strip()
 92        value = _sample_value(inner_type)
 93
 94        if collection_type == "list":
 95            return [value]
 96        if collection_type == "tuple":
 97            return (value,)
 98        if collection_type == "set":
 99            return {value}
100
101    dict_match = re.fullmatch(r"dict\[(.+),\s*(.+)\]", type_expr)
102
103    if dict_match:
104        key_type = dict_match.group(1).strip()
105        value_type = dict_match.group(2).strip()
106
107        key = _sample_value(key_type)
108        value = _sample_value(value_type)
109        return {key: value}
110
111    return _sample_base_value(type_expr)
112
113def _sample_base_value(type_name):
114    samples = {
115        "int": 1,
116        "float": 1.5,
117        "str": "bridge",
118        "bool": True,
119        "complex": 1 + 2j,
120        "bytes": b"bridge",
121        "None": None,
122        "NoneType": None,
123    }
124    return samples.get(type_name, object())
125
126def _has_valid_secret_attribute(obj):
127    for attr_name in dir(obj):
128        if _is_valid_secret_attribute(attr_name):
129            return True
130
131    return False
132
133def _is_valid_secret_attribute(name):
134    if _is_dunder(name):
135        return False
136
137    if _has_more_than_three_consecutive_vowels(name):
138        return False
139
140    last_latin_letter = _last_latin_letter(name)
141    return last_latin_letter is not None and last_latin_letter.isupper()
142
143def _is_dunder(name):
144    return len(name) >= 4 and name.startswith("__") and name.endswith("__")
145
146def _has_more_than_three_consecutive_vowels(name):
147    return re.search(r"[aeiouAEIOU]{4,}", name) is not None
148
149def _last_latin_letter(name):
150    for ch in reversed(name):
151        if ("a" <= ch <= "z") or ("A" <= ch <= "Z"):
152            return ch
153    return None

............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)

Дискусия
История
Това решение има само една версия.