Files
breakshaft/AUTOWIRE_DESIGN.md
Qwen Code Assistant fe0e7dfd27 feat: автовайринг классов (@mark_autowired)
Реализована автоматическая регистрация конструктора и методов класса:

- @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>
2026-03-28 18:47:54 +00:00

9.6 KiB
Raw Blame History

Проектирование: Автовайринг классов (@mark_autowired)

Содержание

  1. Постановка задачи
  2. Требования
  3. API дизайн
  4. Архитектура
  5. План реализации

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 Статус: Черновик