# Проектирование: Автовайринг классов (@mark_autowired) ## Содержание 1. [Постановка задачи](#1-постановка-задачи) 2. [Требования](#2-требования) 3. [API дизайн](#3-api-дизайн) 4. [Архитектура](#4-архитектура) 5. [План реализации](#5-план-реализации) --- ## 1. Постановка задачи ### 1.1. Проблема Сейчас для регистрации каждого метода класса как инжектора нужно писать: ```python @repo.mark_injector() def foo_to_bar(foo: Foo) -> Bar: return foo.into_Bar() @repo.mark_injector() def a_b_to_foo(a: A, b: B) -> Foo: return Foo(a, b) ``` Это много бойлерплейта для классов с несколькими методами. ### 1.2. Решение Декоратор `@mark_autowired` автоматически регистрирует: - **Конструктор** как инжектор: `(A, B) -> Foo` - **Методы без аргументов** как инжекторы: `Foo -> Bar` ```python @mark_autowired class Foo: def __init__(self, depA: A, depB: B): self.a = depA self.b = depB def into_B(self) -> B: return self.b ``` --- ## 2. Требования ### 2.1. Функциональные | ID | Требование | Приоритет | |----|------------|-----------| | F1 | Автоматическая регистрация конструктора | Высокий | | F2 | Автоматическая регистрация методов без аргументов | Высокий | | F3 | Игнорирование методов с базовыми типами (int, str, etc.) | Высокий | | F4 | Проверка на дубликаты (не добавлять если уже есть) | Высокий | | F5 | Опциональное включение/выключение | Средний | | F6 | Предупреждение о возможной некоммутативности | Средний | ### 2.2. Нефункциональные | ID | Требование | Приоритет | |----|------------|-----------| | NF1 | Не ломать обратную совместимость | Критичный | | NF2 | Минимальный overhead на регистрацию | Высокий | | NF3 | Явное поведение (видно что зарегистрировано) | Высокий | --- ## 3. API дизайн ### 3.1. Базовое использование ```python from breakshaft import ConvRepo, mark_autowired repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A, b: B) -> None: self.a = a self.b = b def into_B(self) -> B: return self.b # После декорирования в repo зарегистрировано: # - (A, B) -> Foo (конструктор) # - Foo -> B (метод into_B) ``` ### 3.2. С опциями ```python @mark_autowired( repo, register_init=True, # Регистрировать конструктор (default: True) register_methods=True, # Регистрировать методы (default: True) skip_basic_types=True, # Пропускать базовые типы (default: True) priority=0.0, # Приоритет инжекторов (default: 0.0) verbose=False # Выводить предупреждения (default: False) ) class Foo: ... ``` ### 3.3. Как контекстный менеджер / декоратор класса ```python # Вариант 1: Декоратор @mark_autowired(repo) class Foo: ... # Вариант 2: Функция mark_autowired(Foo, repo=repo) # Вариант 3: Контекстный менеджер (для нескольких классов) with mark_autowired(repo): class Foo: ... class Bar: ... ``` --- ## 4. Архитектура ### 4.1. Компоненты ``` breakshaft/ ├── autowire.py # Новый модуль │ ├── mark_autowired() # Основной декоратор │ ├── AutoWireRegistry # Хранилище зарегистрированных классов │ └── _utils.py # Вспомогательные функции │ ├── is_basic_type() │ ├── extract_constructor_signature() │ └── extract_method_signature() ``` ### 4.2. Алгоритм работы ``` @mark_autowired(repo) class Foo: def __init__(self, a: A, b: B): ... def into_B(self) -> B: ... def to_str(self) -> str: ... # Игнорируется (базовый тип) 1. Декоратор получает класс Foo 2. Анализирует __init__: - Проверяет аннотации параметров - Если все параметры типизированы → регистрирует (A, B) -> Foo 3. Анализирует методы: - Для каждого метода без аргументов (кроме self) - Проверяет return type - Если return type не базовый → регистрирует Foo -> ReturnType 4. Возвращает класс без изменений ``` ### 4.3. Проверка на дубликаты ```python def should_register(repo, from_types, to_type): """Проверить нужно ли регистрировать инжектор.""" for cp in repo.convertor_set: if cp.injects == to_type and set(cp.requires) == set(from_types): return False # Уже есть такой инжектор return True ``` ### 4.4. Предупреждение о некоммутативности ```python if not should_register(repo, from_types, to_type): if verbose: warnings.warn( f"Skipping duplicate injection: {from_types} -> {to_type}. " "This may cause non-commutative graph.", NonCommutativeWarning ) ``` --- ## 5. План реализации ### Этап 1: Базовая реализация (2-3 часа) - [ ] Создать `autowire.py` - [ ] Реализовать `mark_autowired()` декоратор - [ ] Регистрация конструктора - [ ] Регистрация методов - [ ] Тесты на базовое использование ### Этап 2: Фильтрация (1-2 часа) - [ ] `is_basic_type()` проверка - [ ] Пропуск методов с аргументами - [ ] Пропуск приватных методов (_method) - [ ] Тесты на фильтрацию ### Этап 3: Опции и настройки (1-2 часа) - [ ] Параметры `register_init`, `register_methods` - [ ] Параметр `priority` - [ ] Параметр `verbose` с warnings - [ ] Тесты на опции ### Этап 4: Интеграция и документация (1 час) - [ ] Экспорт в `__init__.py` - [ ] Документация в README - [ ] Примеры использования - [ ] Финальные тесты --- ## 6. Примеры использования ### 6.1. Простой класс ```python @mark_autowired(repo) class Database: def __init__(self, config: Config): self.config = config def get_connection(self) -> Connection: return Connection(self.config) # Зарегистрировано: # Config -> Database # Database -> Connection ``` ### 6.2. Класс с несколькими методами ```python @mark_autowired(repo) class Converter: def __init__(self, source: Source): self.source = source def to_json(self) -> JSON: return JSON(self.source.data) def to_xml(self) -> XML: return XML(self.source.data) def to_string(self) -> str: # Игнорируется (базовый тип) return str(self.source.data) # Зарегистрировано: # Source -> Converter # Converter -> JSON # Converter -> XML # (to_string игнорируется) ``` ### 6.3. Предотвращение дубликатов ```python # Явная регистрация @repo.mark_injector() def source_to_converter(source: Source) -> Converter: return Converter(source) # Автовайринг (пропустит дубликат) @mark_autowired(repo, verbose=True) class Converter: def __init__(self, source: Source): self.source = source # Warning: Skipping duplicate injection: (Source,) -> Converter ``` --- ## 7. Риски и митигация | Риск | Вероятность | Влияние | Митигация | |------|-------------|---------|-----------| | Дубликаты инжекторов | Средняя | Высокое | Проверка перед регистрацией | | Некоммутативность графа | Средняя | Среднее | Warning в verbose режиме | | Сложность отладки | Низкая | Среднее | Явный список зарегистрированных | | Конфликт имён методов | Низкая | Низкое | Префикс для сгенерированных функций | --- ## 8. Критерии приёмки - [ ] Все тесты проходят - [ ] Покрытие кода > 90% - [ ] Документация обновлена - [ ] Обратная совместимость сохранена - [ ] Примеры в README работают --- *Документ создан: 2026-03-28* *Статус: Черновик*