Основные изменения: - Добавлена иерархия исключений (17 классов) с кодами ошибок и контекстом - Улучшена обработка ошибок: детальные сообщения с подсказками - Добавлено 24 теста для экстремальных случаев (комбинаторика, циклы, async) - Добавлено 23 теста для системы обработки ошибок - Исправлен баг с optional-аргументами в renderer.py - Обновлены импорты в тестах (src.breakshaft → breakshaft) Документация: - ERROR_DESIGN.md — проектирование системы ошибок - COMMUTATIVITY_DESIGN.md — анализ проблемы некоммутативности (10 вариантов решений) Файлы: - src/breakshaft/exceptions.py (новый) — модуль исключений - tests/test_error_handling.py (новый) — тесты ошибок - tests/test_extreme_cases.py (новый) — экстремальные кейсы Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
231 lines
8.8 KiB
Markdown
231 lines
8.8 KiB
Markdown
# Проектирование системы обработки ошибок для breakshaft
|
||
|
||
## Текущее состояние
|
||
|
||
Сейчас библиотека использует только `ValueError` и `TypeError` с краткими сообщениями. Это затрудняет отладку, особенно при использовании библиотеки как системы внедрения зависимостей.
|
||
|
||
### Существующие ошибки:
|
||
1. `ValueError: Function ... provided as injector, but return-type is not specified`
|
||
2. `ValueError: All callgraph subgraphs must be solved for callseq generation`
|
||
3. `ValueError: Unable to compute conversion graph on ...`
|
||
4. `ValueError: Unable to select conversion path`
|
||
5. `ValueError: Conversion path is not commutative`
|
||
6. `TypeError: Param ... must be type-annotated`
|
||
|
||
---
|
||
|
||
## Категории ошибок
|
||
|
||
### 1. Ошибки регистрации инжекторов (Injector Registration Errors)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| `INJECTOR_001` | MissingReturnType | У функции-инжектора не указан тип возврата |
|
||
| `INJECTOR_002` | MissingParamType | У параметра инжектора не указан тип |
|
||
| `INJECTOR_003` | CircularDependency | Обнаружена циклическая зависимость при регистрации |
|
||
| `INJECTOR_004` | DuplicateInjector | Зарегистрировано несколько одинаковых инжекторов |
|
||
| `INJECTOR_005` | InvalidInjectorSignature | Некорректная сигнатура функции-инжектора |
|
||
|
||
### 2. Ошибки построения графа (Graph Construction Errors)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| `GRAPH_001` | NoConversionPath | Невозможно построить путь преобразования между типами |
|
||
| `GRAPH_002` | AmbiguousPath | Найдено несколько путей преобразования (некоммутативность) |
|
||
| `GRAPH_003` | CycleDetected | Обнаружен цикл в графе преобразований |
|
||
| `GRAPH_004` | TypeMismatch | Тип аргумента не соответствует ожидаемому |
|
||
| `GRAPH_005` | MissingDependency | Зависимость не может быть удовлетворена |
|
||
|
||
### 3. Ошибки генерации кода (Code Generation Errors)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| `CODEGEN_001` | TemplateRenderError | Ошибка при рендеринге Jinja2-шаблона |
|
||
| `CODEGEN_002` | InvalidGeneratedCode | Сгенерированный код некорректен |
|
||
| `CODEGEN_003` | NameCollision | Конфликт имён в сгенерированном коде |
|
||
|
||
### 4. Ошибки выполнения (Runtime Errors)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| `RUNTIME_001` | InjectorCallFailed | Ошибка при вызове функции-инжектора |
|
||
| `RUNTIME_002` | ContextManagerError | Ошибка при входе/выходе из контекст-менеджера |
|
||
| `RUNTIME_003` | AsyncExecutionError | Ошибка при выполнении асинхронной операции |
|
||
|
||
### 5. Ошибки конфигурации (Configuration Errors)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| `CONFIG_001` | InvalidOptions | Некорректные опции (force_commutative + allow_async и т.д.) |
|
||
| `CONFIG_002` | IncompatibleSettings | Несовместимые настройки |
|
||
|
||
---
|
||
|
||
## Иерархия исключений
|
||
|
||
```
|
||
BreakshaftError (базовое)
|
||
├── InjectorError
|
||
│ ├── MissingReturnType
|
||
│ ├── MissingParamType
|
||
│ ├── CircularDependency
|
||
│ ├── DuplicateInjector
|
||
│ └── InvalidInjectorSignature
|
||
├── GraphError
|
||
│ ├── NoConversionPath
|
||
│ ├── AmbiguousPath
|
||
│ ├── CycleDetected
|
||
│ ├── TypeMismatch
|
||
│ └── MissingDependency
|
||
├── CodegenError
|
||
│ ├── TemplateRenderError
|
||
│ ├── InvalidGeneratedCode
|
||
│ └── NameCollision
|
||
├── RuntimeError
|
||
│ ├── InjectorCallFailed
|
||
│ ├── ContextManagerError
|
||
│ └── AsyncExecutionError
|
||
└── ConfigurationError
|
||
├── InvalidOptions
|
||
└── IncompatibleSettings
|
||
```
|
||
|
||
---
|
||
|
||
## Детальное описание ошибок с примерами
|
||
|
||
### INJECTOR_001: MissingReturnType
|
||
```python
|
||
@repo.mark_injector()
|
||
def int_to_a(i: int): # Нет -> A
|
||
return A(i)
|
||
|
||
# Ошибка: INJECTOR_001: Function 'int_to_a' missing return type annotation
|
||
# Решение: Добавить аннотацию возврата: def int_to_a(i: int) -> A:
|
||
```
|
||
|
||
### INJECTOR_002: MissingParamType
|
||
```python
|
||
@repo.mark_injector()
|
||
def convert(value) -> A: # Нет типа у параметра
|
||
return A(value)
|
||
|
||
# Ошибка: INJECTOR_002: Parameter 'value' missing type annotation
|
||
# Решение: Добавить аннотацию: def convert(value: int) -> A:
|
||
```
|
||
|
||
### INJECTOR_003: CircularDependency
|
||
```python
|
||
@repo.mark_injector()
|
||
def a_to_b(a: A) -> B: ...
|
||
|
||
@repo.mark_injector()
|
||
def b_to_c(b: B) -> C: ...
|
||
|
||
@repo.mark_injector()
|
||
def c_to_a(c: C) -> A: ... # Замыкает цикл
|
||
|
||
# Ошибка: INJECTOR_003: Circular dependency detected: A -> B -> C -> A
|
||
# Решение: Разорвать цикл или использовать force_commutative=False
|
||
```
|
||
|
||
### GRAPH_001: NoConversionPath
|
||
```python
|
||
@repo.mark_injector()
|
||
def int_to_a(i: int) -> A: ...
|
||
|
||
def consumer(dep: B) -> str: ... # B нельзя получить из int
|
||
|
||
repo.get_conversion((int,), consumer)
|
||
|
||
# Ошибка: GRAPH_001: No conversion path from (int,) to consumer
|
||
# Доступные типы: {A}
|
||
# Требуемые типы: {B}
|
||
# Решение: Добавить инжектор для получения B
|
||
```
|
||
|
||
### GRAPH_002: AmbiguousPath
|
||
```python
|
||
@repo.mark_injector()
|
||
def int_to_a_direct(i: int) -> A:
|
||
return A(i)
|
||
|
||
@repo.mark_injector()
|
||
def int_to_b(i: int) -> B:
|
||
return B(float(i))
|
||
|
||
@repo.mark_injector()
|
||
def b_to_a(b: B) -> A:
|
||
return A(int(b.b))
|
||
|
||
def consumer(dep: A) -> int: ...
|
||
|
||
repo.get_conversion((int,), consumer, force_commutative=True)
|
||
# Два пути: int->A и int->B->A дают разные результаты
|
||
|
||
# Ошибка: GRAPH_002: Ambiguous conversion path (non-commutative graph)
|
||
# Путь 1: int -> A (прямой)
|
||
# Путь 2: int -> B -> A (через B)
|
||
# Решение: Использовать force_commutative=False или убрать один из путей
|
||
```
|
||
|
||
### GRAPH_004: TypeMismatch
|
||
```python
|
||
@repo.mark_injector()
|
||
def convert(s: str) -> A: ...
|
||
|
||
repo.get_conversion((int,), ...) # int нельзя преобразовать в str
|
||
|
||
# Ошибка: GRAPH_004: Type mismatch - expected str, got int
|
||
```
|
||
|
||
### CONFIG_001: InvalidOptions
|
||
```python
|
||
repo.get_conversion(..., allow_async=False, force_async=True)
|
||
|
||
# Ошибка: CONFIG_001: Invalid options - force_async=True but allow_async=False
|
||
# Решение: Установить allow_async=True или force_async=False
|
||
```
|
||
|
||
---
|
||
|
||
## Формат сообщений об ошибках
|
||
|
||
Каждое сообщение должно содержать:
|
||
1. **Код ошибки** (для поиска в документации)
|
||
2. **Краткое описание** (что произошло)
|
||
3. **Контекст** (где произошло, какие типы задействованы)
|
||
4. **Подсказку** (как исправить)
|
||
|
||
### Пример формата:
|
||
```
|
||
BreakshaftError [GRAPH_001]: No conversion path found
|
||
|
||
Cannot build conversion from source types to target function.
|
||
|
||
Context:
|
||
Source types: (int, float)
|
||
Target function: my_module.consumer
|
||
Required types: {A, B}
|
||
Available types: {A, C, D}
|
||
Missing types: {B}
|
||
|
||
Suggestions:
|
||
1. Add an injector that produces type 'B'
|
||
2. Check if type annotations are correct
|
||
3. Use force_commutative=False if multiple paths are expected
|
||
|
||
Documentation: https://breakshaft.readthedocs.io/errors/GRAPH_001
|
||
```
|
||
|
||
---
|
||
|
||
## План реализации
|
||
|
||
1. **Создать модуль `exceptions.py`** с иерархией исключений
|
||
2. **Добавить базовый класс `BreakshaftError`** с форматированием сообщений
|
||
3. **Заменить все `raise ValueError`** на специфичные исключения
|
||
4. **Добавить дополнительные проверки** (валидация на входе)
|
||
5. **Написать тесты** для всех типов ошибок
|
||
6. **Добавить документацию** по ошибкам
|