Основные изменения: - Добавлена иерархия исключений (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>
8.8 KiB
8.8 KiB
Проектирование системы обработки ошибок для breakshaft
Текущее состояние
Сейчас библиотека использует только ValueError и TypeError с краткими сообщениями. Это затрудняет отладку, особенно при использовании библиотеки как системы внедрения зависимостей.
Существующие ошибки:
ValueError: Function ... provided as injector, but return-type is not specifiedValueError: All callgraph subgraphs must be solved for callseq generationValueError: Unable to compute conversion graph on ...ValueError: Unable to select conversion pathValueError: Conversion path is not commutativeTypeError: 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
@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
@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
@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
@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
@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
@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
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
Формат сообщений об ошибках
Каждое сообщение должно содержать:
- Код ошибки (для поиска в документации)
- Краткое описание (что произошло)
- Контекст (где произошло, какие типы задействованы)
- Подсказку (как исправить)
Пример формата:
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
План реализации
- Создать модуль
exceptions.pyс иерархией исключений - Добавить базовый класс
BreakshaftErrorс форматированием сообщений - Заменить все
raise ValueErrorна специфичные исключения - Добавить дополнительные проверки (валидация на входе)
- Написать тесты для всех типов ошибок
- Добавить документацию по ошибкам