Реализована автоматическая регистрация конструктора и методов класса:
- @mark_autowired(repo) декоратор
- register_init=True: регистрировать конструктор (A,B)->Foo
- register_methods=True: регистрировать методы Foo->B
- skip_basic_types=True: пропускать базовые типы (int, str)
- priority: приоритет инжекторов
- verbose: предупреждения о дубликатах
Файлы:
- autowire.py: mark_autowired(), NonCommutativeWarning
- test_autowire.py: 19 тестов
- __init__.py: экспорт mark_autowired, NonCommutativeWarning
- AUTOWIRE_DESIGN.md: документация
Пример:
@mark_autowired(repo)
class Foo:
def __init__(self, a: A, b: B): ...
def into_B(self) -> B: ...
# Зарегистрировано: (A,B)->Foo, Foo->B
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
9.6 KiB
9.6 KiB
Проектирование: Автовайринг классов (@mark_autowired)
Содержание
1. Постановка задачи
1.1. Проблема
Сейчас для регистрации каждого метода класса как инжектора нужно писать:
@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
@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. Базовое использование
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. С опциями
@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. Как контекстный менеджер / декоратор класса
# Вариант 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. Проверка на дубликаты
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. Предупреждение о некоммутативности
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. Простой класс
@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. Класс с несколькими методами
@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. Предотвращение дубликатов
# Явная регистрация
@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 Статус: Черновик