feat: масштабное улучшение системы обработки ошибок и тестирования
Основные изменения: - Добавлена иерархия исключений (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>
This commit is contained in:
230
ERROR_DESIGN.md
Normal file
230
ERROR_DESIGN.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# Проектирование системы обработки ошибок для 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. **Добавить документацию** по ошибкам
|
||||
Reference in New Issue
Block a user