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>
This commit is contained in:
299
AUTOWIRE_DESIGN.md
Normal file
299
AUTOWIRE_DESIGN.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Проектирование: Автовайринг классов (@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*
|
||||
*Статус: Черновик*
|
||||
Reference in New Issue
Block a user