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

300 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Проектирование: Автовайринг классов (@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*
*Статус: Черновик*