mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-2.1.3.dist-info/RECORD +18 -0
- mcp_proxy_adapter-2.1.3.dist-info/top_level.txt +1 -0
- docs/README.md +0 -172
- docs/README_ru.md +0 -172
- docs/architecture.md +0 -251
- docs/architecture_ru.md +0 -343
- docs/command_development.md +0 -250
- docs/command_development_ru.md +0 -593
- docs/deployment.md +0 -251
- docs/deployment_ru.md +0 -1298
- docs/examples.md +0 -254
- docs/examples_ru.md +0 -401
- docs/mcp_proxy_adapter.md +0 -251
- docs/mcp_proxy_adapter_ru.md +0 -405
- docs/quickstart.md +0 -251
- docs/quickstart_ru.md +0 -397
- docs/testing.md +0 -255
- docs/testing_ru.md +0 -469
- docs/validation_ru.md +0 -287
- examples/analyze_config.py +0 -141
- examples/basic_integration.py +0 -161
- examples/docstring_and_schema_example.py +0 -60
- examples/extension_example.py +0 -60
- examples/help_best_practices.py +0 -67
- examples/help_usage.py +0 -64
- examples/mcp_proxy_client.py +0 -131
- examples/mcp_proxy_config.json +0 -175
- examples/openapi_server.py +0 -369
- examples/project_structure_example.py +0 -47
- examples/testing_example.py +0 -53
- mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
- mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
- scripts/code_analyzer/code_analyzer.py +0 -328
- scripts/code_analyzer/register_commands.py +0 -446
- scripts/publish.py +0 -85
- tests/conftest.py +0 -12
- tests/test_adapter.py +0 -529
- tests/test_adapter_coverage.py +0 -274
- tests/test_basic_dispatcher.py +0 -169
- tests/test_command_registry.py +0 -328
- tests/test_examples.py +0 -32
- tests/test_mcp_proxy_adapter.py +0 -568
- tests/test_mcp_proxy_adapter_basic.py +0 -262
- tests/test_part1.py +0 -348
- tests/test_part2.py +0 -524
- tests/test_schema.py +0 -358
- tests/test_simple_adapter.py +0 -251
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/licenses/LICENSE +0 -0
docs/architecture_ru.md
DELETED
@@ -1,343 +0,0 @@
|
|
1
|
-
# Архитектура Command Registry
|
2
|
-
|
3
|
-
Данный документ описывает архитектуру системы Command Registry, ее ключевые компоненты, их взаимодействие и принципы расширения.
|
4
|
-
|
5
|
-
## Обзор архитектуры
|
6
|
-
|
7
|
-
Command Registry - это модульная система, построенная вокруг концепции централизованного хранения и управления командами. Архитектура обеспечивает гибкость, расширяемость и соблюдение принципов SOLID.
|
8
|
-
|
9
|
-
Ключевые возможности системы:
|
10
|
-
|
11
|
-
1. **Определение команд как Python-функций** с использованием типизации и докстрингов
|
12
|
-
2. **Извлечение метаданных** из сигнатур функций и их документации
|
13
|
-
3. **Регистрация команд** в центральном реестре
|
14
|
-
4. **Предоставление унифицированного интерфейса** для выполнения команд
|
15
|
-
5. **Генерация документации API** на основе метаданных команд
|
16
|
-
6. **Экспорт команд** через различные протоколы (REST, JSON-RPC, CLI и т.д.)
|
17
|
-
|
18
|
-
## Компоненты системы
|
19
|
-
|
20
|
-

|
21
|
-
|
22
|
-
### Основные компоненты:
|
23
|
-
|
24
|
-
1. **Command Definition** - Определение команды (функция Python с типизацией и докстрингами)
|
25
|
-
2. **Dispatcher Component** - Диспетчер команд, отвечающий за их регистрацию и выполнение
|
26
|
-
3. **Metadata Extractor** - Извлекатель метаданных из докстрингов и сигнатур функций
|
27
|
-
4. **Protocol Adapter** - Адаптер для экспорта команд через различные протоколы
|
28
|
-
|
29
|
-
### CommandRegistry
|
30
|
-
|
31
|
-
Центральный компонент системы, который:
|
32
|
-
|
33
|
-
- Инициализирует и конфигурирует диспетчеры
|
34
|
-
- Предоставляет интерфейс для регистрации команд
|
35
|
-
- Управляет метаданными команд
|
36
|
-
- Координирует взаимодействие между компонентами
|
37
|
-
|
38
|
-
## Жизненный цикл команды
|
39
|
-
|
40
|
-
### 1. Определение команды
|
41
|
-
|
42
|
-
```python
|
43
|
-
def calculate_total(
|
44
|
-
prices: List[float],
|
45
|
-
discount: float = 0.0,
|
46
|
-
tax_rate: float = 0.0
|
47
|
-
) -> float:
|
48
|
-
"""
|
49
|
-
Рассчитывает общую стоимость с учетом скидки и налога.
|
50
|
-
|
51
|
-
Args:
|
52
|
-
prices: Список цен товаров
|
53
|
-
discount: Скидка в процентах (0-100)
|
54
|
-
tax_rate: Налоговая ставка в процентах (0-100)
|
55
|
-
|
56
|
-
Returns:
|
57
|
-
Общая стоимость с учетом скидки и налога
|
58
|
-
"""
|
59
|
-
subtotal = sum(prices)
|
60
|
-
discounted = subtotal * (1 - discount / 100)
|
61
|
-
total = discounted * (1 + tax_rate / 100)
|
62
|
-
return round(total, 2)
|
63
|
-
```
|
64
|
-
|
65
|
-
### 2. Регистрация команды
|
66
|
-
|
67
|
-
```python
|
68
|
-
from command_registry import CommandRegistry
|
69
|
-
from command_registry.dispatchers import CommandDispatcher
|
70
|
-
|
71
|
-
# Создание реестра команд
|
72
|
-
registry = CommandRegistry(CommandDispatcher())
|
73
|
-
|
74
|
-
# Регистрация команды
|
75
|
-
registry.register_command("calculate_total", calculate_total)
|
76
|
-
```
|
77
|
-
|
78
|
-
### 3. Выполнение команды
|
79
|
-
|
80
|
-
```python
|
81
|
-
# Выполнение команды
|
82
|
-
result = registry.execute(
|
83
|
-
"calculate_total",
|
84
|
-
{
|
85
|
-
"prices": [10.0, 20.0, 30.0],
|
86
|
-
"discount": 10.0,
|
87
|
-
"tax_rate": 7.0
|
88
|
-
}
|
89
|
-
)
|
90
|
-
print(result) # 57.33
|
91
|
-
```
|
92
|
-
|
93
|
-
### 4. Экспорт через API
|
94
|
-
|
95
|
-
```python
|
96
|
-
from fastapi import FastAPI
|
97
|
-
from command_registry.adapters import RESTAdapter
|
98
|
-
|
99
|
-
app = FastAPI()
|
100
|
-
adapter = RESTAdapter(registry)
|
101
|
-
adapter.register_endpoints(app)
|
102
|
-
```
|
103
|
-
|
104
|
-
## Диаграммы потока данных
|
105
|
-
|
106
|
-
### Процесс регистрации команды
|
107
|
-
|
108
|
-
```
|
109
|
-
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
|
110
|
-
│ │ │ │ │ │
|
111
|
-
│ Python функция ├─────►│ Metadata Extractor ├─────►│ Метаданные │
|
112
|
-
│ │ │ │ │ │
|
113
|
-
└─────────────────┘ └────────────────────┘ └────────┬────────┘
|
114
|
-
│
|
115
|
-
▼
|
116
|
-
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
|
117
|
-
│ │ │ │ │ │
|
118
|
-
│ CommandRegistry│◄─────┤ Валидация данных │◄─────┤ Параметры │
|
119
|
-
│ │ │ │ │ │
|
120
|
-
└────────┬────────┘ └────────────────────┘ └─────────────────┘
|
121
|
-
│
|
122
|
-
▼
|
123
|
-
┌─────────────────┐
|
124
|
-
│ │
|
125
|
-
│ Dispatcher │
|
126
|
-
│ │
|
127
|
-
└─────────────────┘
|
128
|
-
```
|
129
|
-
|
130
|
-
### Процесс выполнения команды
|
131
|
-
|
132
|
-
```
|
133
|
-
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
|
134
|
-
│ │ │ │ │ │
|
135
|
-
│ Имя команды │ │ │ │ Валидация │
|
136
|
-
│ + параметры ├─────►│ CommandRegistry ├─────►│ параметров │
|
137
|
-
│ │ │ │ │ │
|
138
|
-
└─────────────────┘ └────────────────────┘ └────────┬────────┘
|
139
|
-
│
|
140
|
-
▼
|
141
|
-
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
|
142
|
-
│ │ │ │ │ │
|
143
|
-
│ Результат │◄─────┤ Обработка ошибок │◄─────┤ Dispatcher │
|
144
|
-
│ │ │ │ │ (выполнение) │
|
145
|
-
└─────────────────┘ └────────────────────┘ └─────────────────┘
|
146
|
-
```
|
147
|
-
|
148
|
-
### Генерация документации API
|
149
|
-
|
150
|
-
```
|
151
|
-
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
|
152
|
-
│ │ │ │ │ │
|
153
|
-
│ Метаданные │ │ Schema Generator │ │ OpenAPI/ │
|
154
|
-
│ команд ├─────►│ ├─────►│ JSON Schema │
|
155
|
-
│ │ │ │ │ │
|
156
|
-
└─────────────────┘ └────────────────────┘ └────────┬────────┘
|
157
|
-
│
|
158
|
-
▼
|
159
|
-
┌─────────────────┐
|
160
|
-
│ │
|
161
|
-
│ API Docs UI │
|
162
|
-
│ (Swagger/ │
|
163
|
-
│ ReDoc) │
|
164
|
-
└─────────────────┘
|
165
|
-
```
|
166
|
-
|
167
|
-
## Расширение системы
|
168
|
-
|
169
|
-
### Создание собственного диспетчера
|
170
|
-
|
171
|
-
```python
|
172
|
-
from command_registry.dispatchers import BaseDispatcher
|
173
|
-
from typing import Dict, Any, List, Optional, Callable
|
174
|
-
|
175
|
-
class MyCustomDispatcher(BaseDispatcher):
|
176
|
-
def __init__(self):
|
177
|
-
self._commands = {}
|
178
|
-
self._info = {}
|
179
|
-
|
180
|
-
def register_handler(
|
181
|
-
self,
|
182
|
-
command_name: str,
|
183
|
-
handler: Callable,
|
184
|
-
description: str = None,
|
185
|
-
summary: str = None,
|
186
|
-
params: Dict[str, Any] = None
|
187
|
-
) -> None:
|
188
|
-
self._commands[command_name] = handler
|
189
|
-
self._info[command_name] = {
|
190
|
-
"description": description,
|
191
|
-
"summary": summary,
|
192
|
-
"params": params or {}
|
193
|
-
}
|
194
|
-
|
195
|
-
def execute(self, command_name: str, params: Dict[str, Any] = None) -> Any:
|
196
|
-
if command_name not in self._commands:
|
197
|
-
raise ValueError(f"Command '{command_name}' not found")
|
198
|
-
|
199
|
-
handler = self._commands[command_name]
|
200
|
-
return handler(**params or {})
|
201
|
-
|
202
|
-
def get_valid_commands(self) -> List[str]:
|
203
|
-
return list(self._commands.keys())
|
204
|
-
|
205
|
-
def get_command_info(self, command_name: str) -> Optional[Dict[str, Any]]:
|
206
|
-
return self._info.get(command_name)
|
207
|
-
|
208
|
-
def get_commands_info(self) -> Dict[str, Dict[str, Any]]:
|
209
|
-
return self._info
|
210
|
-
```
|
211
|
-
|
212
|
-
### Создание собственного адаптера протокола
|
213
|
-
|
214
|
-
```python
|
215
|
-
from command_registry import CommandRegistry
|
216
|
-
from typing import Dict, Any
|
217
|
-
|
218
|
-
class GraphQLAdapter:
|
219
|
-
def __init__(self, registry: CommandRegistry):
|
220
|
-
self.registry = registry
|
221
|
-
|
222
|
-
def generate_schema(self) -> str:
|
223
|
-
"""Генерирует GraphQL схему на основе метаданных команд."""
|
224
|
-
commands_info = self.registry.get_all_commands_info()
|
225
|
-
schema_types = []
|
226
|
-
query_fields = []
|
227
|
-
|
228
|
-
for cmd_name, info in commands_info.items():
|
229
|
-
# Генерация типов для входных и выходных данных
|
230
|
-
input_type = self._generate_input_type(cmd_name, info["params"])
|
231
|
-
output_type = self._generate_output_type(cmd_name, info.get("returns"))
|
232
|
-
|
233
|
-
schema_types.extend([input_type, output_type])
|
234
|
-
|
235
|
-
# Добавление поля в Query
|
236
|
-
query_fields.append(
|
237
|
-
f"{cmd_name}(input: {cmd_name}Input): {cmd_name}Output"
|
238
|
-
)
|
239
|
-
|
240
|
-
# Формирование итоговой схемы
|
241
|
-
schema = "\n".join(schema_types)
|
242
|
-
schema += f"\ntype Query {{\n {chr(10).join(query_fields)}\n}}"
|
243
|
-
|
244
|
-
return schema
|
245
|
-
|
246
|
-
def _generate_input_type(self, cmd_name: str, params: Dict[str, Any]) -> str:
|
247
|
-
fields = []
|
248
|
-
for name, param_info in params.items():
|
249
|
-
field_type = self._map_type(param_info.get("type", "String"))
|
250
|
-
required = "!" if param_info.get("required", False) else ""
|
251
|
-
fields.append(f" {name}: {field_type}{required}")
|
252
|
-
|
253
|
-
return f"input {cmd_name}Input {{\n{chr(10).join(fields)}\n}}"
|
254
|
-
|
255
|
-
def _generate_output_type(self, cmd_name: str, returns_info: Dict[str, Any]) -> str:
|
256
|
-
return_type = "String"
|
257
|
-
if returns_info:
|
258
|
-
return_type = self._map_type(returns_info.get("type", "String"))
|
259
|
-
|
260
|
-
return (
|
261
|
-
f"type {cmd_name}Output {{\n"
|
262
|
-
f" result: {return_type}\n"
|
263
|
-
f" error: String\n"
|
264
|
-
f"}}"
|
265
|
-
)
|
266
|
-
|
267
|
-
def _map_type(self, python_type: str) -> str:
|
268
|
-
"""Преобразует типы Python в типы GraphQL."""
|
269
|
-
type_mapping = {
|
270
|
-
"str": "String",
|
271
|
-
"int": "Int",
|
272
|
-
"float": "Float",
|
273
|
-
"bool": "Boolean",
|
274
|
-
"list": "List",
|
275
|
-
"dict": "JSON",
|
276
|
-
# Добавьте другие маппинги по необходимости
|
277
|
-
}
|
278
|
-
return type_mapping.get(python_type, "String")
|
279
|
-
```
|
280
|
-
|
281
|
-
## Лучшие практики
|
282
|
-
|
283
|
-
### Организация команд в модули
|
284
|
-
|
285
|
-
Рекомендуется группировать связанные команды в модули по их функциональности:
|
286
|
-
|
287
|
-
```
|
288
|
-
commands/
|
289
|
-
├── __init__.py
|
290
|
-
├── user_commands.py # Команды для работы с пользователями
|
291
|
-
├── product_commands.py # Команды для работы с товарами
|
292
|
-
└── order_commands.py # Команды для работы с заказами
|
293
|
-
```
|
294
|
-
|
295
|
-
### Регистрация команд
|
296
|
-
|
297
|
-
Автоматическая регистрация из модуля:
|
298
|
-
|
299
|
-
```python
|
300
|
-
registry.scan_module("myapp.commands")
|
301
|
-
```
|
302
|
-
|
303
|
-
Ручная регистрация отдельных функций:
|
304
|
-
|
305
|
-
```python
|
306
|
-
from myapp.commands.user_commands import create_user, update_user, delete_user
|
307
|
-
|
308
|
-
registry.register_command("create_user", create_user)
|
309
|
-
registry.register_command("update_user", update_user)
|
310
|
-
registry.register_command("delete_user", delete_user)
|
311
|
-
```
|
312
|
-
|
313
|
-
### Обработка ошибок
|
314
|
-
|
315
|
-
```python
|
316
|
-
def divide_numbers(a: float, b: float) -> float:
|
317
|
-
"""
|
318
|
-
Делит число a на число b.
|
319
|
-
|
320
|
-
Args:
|
321
|
-
a: Делимое
|
322
|
-
b: Делитель (не должен быть равен 0)
|
323
|
-
|
324
|
-
Returns:
|
325
|
-
Результат деления a на b
|
326
|
-
|
327
|
-
Raises:
|
328
|
-
ValueError: Если делитель равен 0
|
329
|
-
"""
|
330
|
-
if b == 0:
|
331
|
-
raise ValueError("Делитель не может быть равен 0")
|
332
|
-
return a / b
|
333
|
-
```
|
334
|
-
|
335
|
-
## Заключение
|
336
|
-
|
337
|
-
Архитектура Command Registry обеспечивает:
|
338
|
-
|
339
|
-
1. **Четкое разделение ответственности** между компонентами системы
|
340
|
-
2. **Расширяемость** через добавление новых диспетчеров и адаптеров
|
341
|
-
3. **Простоту использования** для разработчиков команд
|
342
|
-
4. **Автоматизацию** извлечения метаданных и валидации
|
343
|
-
5. **Интероперабельность** с различными протоколами и форматами
|
docs/command_development.md
DELETED
@@ -1,250 +0,0 @@
|
|
1
|
-
# Command Development Guide
|
2
|
-
|
3
|
-
This guide describes best practices and recommendations for developing commands for the Command Registry system.
|
4
|
-
|
5
|
-
## Core Principles
|
6
|
-
|
7
|
-
When developing commands for Command Registry, follow these principles:
|
8
|
-
|
9
|
-
1. **Single Responsibility**: a command should do one thing and do it well
|
10
|
-
2. **Declarative**: a command should be self-documenting through type hints and docstrings
|
11
|
-
3. **Independence**: a command should not depend on other commands
|
12
|
-
4. **Idempotency**: repeated execution of a command with the same parameters should yield identical results
|
13
|
-
5. **Validation**: a command should validate input data before execution
|
14
|
-
|
15
|
-
## Command Structure
|
16
|
-
|
17
|
-
Recommended command structure:
|
18
|
-
|
19
|
-
```python
|
20
|
-
from typing import Dict, List, Optional, Any, Union, Tuple
|
21
|
-
import logging
|
22
|
-
|
23
|
-
logger = logging.getLogger(__name__)
|
24
|
-
|
25
|
-
def command_name(
|
26
|
-
required_param: str,
|
27
|
-
optional_param: Optional[int] = None,
|
28
|
-
*,
|
29
|
-
keyword_only_param: bool = False
|
30
|
-
) -> Dict[str, Any]:
|
31
|
-
"""
|
32
|
-
Brief command description (one sentence).
|
33
|
-
|
34
|
-
Detailed command description explaining its purpose,
|
35
|
-
operational features, and possible side effects.
|
36
|
-
|
37
|
-
Args:
|
38
|
-
required_param: Description of required parameter
|
39
|
-
optional_param: Description of optional parameter
|
40
|
-
keyword_only_param: Description of keyword-only parameter
|
41
|
-
|
42
|
-
Returns:
|
43
|
-
Description of return value
|
44
|
-
|
45
|
-
Raises:
|
46
|
-
ValueError: When validation error occurs
|
47
|
-
RuntimeError: When execution error occurs
|
48
|
-
|
49
|
-
Examples:
|
50
|
-
>>> command_name("value", 42, keyword_only_param=True)
|
51
|
-
{'status': 'success', 'result': 'value_processed'}
|
52
|
-
"""
|
53
|
-
# Log execution start
|
54
|
-
logger.debug(f"Executing command_name with params: {required_param}, {optional_param}, {keyword_only_param}")
|
55
|
-
|
56
|
-
# Parameter validation
|
57
|
-
if not required_param:
|
58
|
-
raise ValueError("required_param cannot be empty")
|
59
|
-
|
60
|
-
if optional_param is not None and optional_param < 0:
|
61
|
-
raise ValueError("optional_param must be non-negative")
|
62
|
-
|
63
|
-
# Execute main logic
|
64
|
-
try:
|
65
|
-
# Main command logic
|
66
|
-
result = process_data(required_param, optional_param, keyword_only_param)
|
67
|
-
|
68
|
-
# Log successful execution
|
69
|
-
logger.info(f"Command command_name executed successfully with result: {result}")
|
70
|
-
|
71
|
-
return {
|
72
|
-
"status": "success",
|
73
|
-
"result": result
|
74
|
-
}
|
75
|
-
except Exception as e:
|
76
|
-
# Log error
|
77
|
-
logger.error(f"Error executing command command_name: {str(e)}", exc_info=True)
|
78
|
-
|
79
|
-
# Propagate exception for handling above
|
80
|
-
raise RuntimeError(f"Failed to execute command: {str(e)}") from e
|
81
|
-
```
|
82
|
-
|
83
|
-
## Best Practices
|
84
|
-
|
85
|
-
### Type Hints
|
86
|
-
|
87
|
-
1. **Use type hints for all parameters and return values**:
|
88
|
-
|
89
|
-
```python
|
90
|
-
def process_data(data: List[Dict[str, Any]], limit: Optional[int] = None) -> Tuple[List[Dict[str, Any]], int]:
|
91
|
-
# ...
|
92
|
-
```
|
93
|
-
|
94
|
-
2. **Use complex types from the `typing` module**:
|
95
|
-
|
96
|
-
```python
|
97
|
-
from typing import Dict, List, Optional, Union, Callable, TypeVar, Generic
|
98
|
-
|
99
|
-
T = TypeVar('T')
|
100
|
-
|
101
|
-
def filter_items(
|
102
|
-
items: List[T],
|
103
|
-
predicate: Callable[[T], bool]
|
104
|
-
) -> List[T]:
|
105
|
-
# ...
|
106
|
-
```
|
107
|
-
|
108
|
-
3. **Define your own types for complex structures**:
|
109
|
-
|
110
|
-
```python
|
111
|
-
from typing import Dict, List, TypedDict, Optional
|
112
|
-
|
113
|
-
class UserData(TypedDict):
|
114
|
-
id: str
|
115
|
-
name: str
|
116
|
-
email: str
|
117
|
-
roles: List[str]
|
118
|
-
profile: Optional[Dict[str, str]]
|
119
|
-
|
120
|
-
def get_user(user_id: str) -> UserData:
|
121
|
-
# ...
|
122
|
-
```
|
123
|
-
|
124
|
-
### Documentation
|
125
|
-
|
126
|
-
1. **Use docstrings to describe the command, its parameters, and return value**:
|
127
|
-
|
128
|
-
```python
|
129
|
-
def calculate_total(prices: List[float], discount: float = 0.0) -> float:
|
130
|
-
"""
|
131
|
-
Calculates total cost with discount.
|
132
|
-
|
133
|
-
Args:
|
134
|
-
prices: List of item prices
|
135
|
-
discount: Discount percentage (from 0.0 to 1.0)
|
136
|
-
|
137
|
-
Returns:
|
138
|
-
Total cost with discount
|
139
|
-
|
140
|
-
Raises:
|
141
|
-
ValueError: If discount is not in range [0, 1]
|
142
|
-
"""
|
143
|
-
```
|
144
|
-
|
145
|
-
2. **Add usage examples in docstring**:
|
146
|
-
|
147
|
-
```python
|
148
|
-
def calculate_total(prices: List[float], discount: float = 0.0) -> float:
|
149
|
-
"""
|
150
|
-
...
|
151
|
-
|
152
|
-
Examples:
|
153
|
-
>>> calculate_total([10.0, 20.0, 30.0])
|
154
|
-
60.0
|
155
|
-
>>> calculate_total([10.0, 20.0, 30.0], discount=0.1)
|
156
|
-
54.0
|
157
|
-
"""
|
158
|
-
```
|
159
|
-
|
160
|
-
### Parameter Validation
|
161
|
-
|
162
|
-
1. **Check parameter correctness at the beginning of the function**:
|
163
|
-
|
164
|
-
```python
|
165
|
-
def update_user(user_id: str, name: Optional[str] = None, email: Optional[str] = None) -> Dict[str, Any]:
|
166
|
-
"""Updates user information."""
|
167
|
-
if not user_id:
|
168
|
-
raise ValueError("user_id cannot be empty")
|
169
|
-
|
170
|
-
if email is not None and "@" not in email:
|
171
|
-
raise ValueError("Invalid email format")
|
172
|
-
|
173
|
-
# Main logic...
|
174
|
-
```
|
175
|
-
|
176
|
-
2. **Use libraries for complex data validation**:
|
177
|
-
|
178
|
-
```python
|
179
|
-
from pydantic import BaseModel, EmailStr, validator
|
180
|
-
|
181
|
-
class UserUpdateParams(BaseModel):
|
182
|
-
user_id: str
|
183
|
-
name: Optional[str] = None
|
184
|
-
email: Optional[EmailStr] = None
|
185
|
-
|
186
|
-
@validator('user_id')
|
187
|
-
def user_id_must_not_be_empty(cls, v):
|
188
|
-
if not v:
|
189
|
-
raise ValueError('user_id cannot be empty')
|
190
|
-
return v
|
191
|
-
|
192
|
-
def update_user(params: UserUpdateParams) -> Dict[str, Any]:
|
193
|
-
# Pydantic has already performed validation
|
194
|
-
# Main logic...
|
195
|
-
```
|
196
|
-
|
197
|
-
### Error Handling
|
198
|
-
|
199
|
-
1. **Use specific exception types**:
|
200
|
-
|
201
|
-
```python
|
202
|
-
def get_user(user_id: str) -> Dict[str, Any]:
|
203
|
-
if not user_id:
|
204
|
-
raise ValueError("user_id cannot be empty")
|
205
|
-
|
206
|
-
user = db.find_user(user_id)
|
207
|
-
if user is None:
|
208
|
-
raise UserNotFoundError(f"User with id {user_id} not found")
|
209
|
-
|
210
|
-
return user
|
211
|
-
```
|
212
|
-
|
213
|
-
2. **Preserve error context using exception chaining**:
|
214
|
-
|
215
|
-
```python
|
216
|
-
def process_order(order_id: str) -> Dict[str, Any]:
|
217
|
-
try:
|
218
|
-
order = get_order(order_id)
|
219
|
-
# Order processing...
|
220
|
-
except OrderNotFoundError as e:
|
221
|
-
raise ProcessingError(f"Failed to process order {order_id}") from e
|
222
|
-
except Exception as e:
|
223
|
-
raise ProcessingError(f"Unexpected error while processing order {order_id}") from e
|
224
|
-
```
|
225
|
-
|
226
|
-
### Logging
|
227
|
-
|
228
|
-
1. **Use logging to track command execution progress**:
|
229
|
-
|
230
|
-
```python
|
231
|
-
import logging
|
232
|
-
|
233
|
-
logger = logging.getLogger(__name__)
|
234
|
-
|
235
|
-
def complex_operation(data: Dict[str, Any]) -> Dict[str, Any]:
|
236
|
-
logger.info(f"Starting complex operation with data: {data}")
|
237
|
-
|
238
|
-
try:
|
239
|
-
# Step 1
|
240
|
-
logger.debug("Performing step 1")
|
241
|
-
result_1 = step_1(data)
|
242
|
-
|
243
|
-
# Step 2
|
244
|
-
logger.debug("Performing step 2")
|
245
|
-
result_2 = step_2(result_1)
|
246
|
-
|
247
|
-
# Final step
|
248
|
-
logger.debug("Performing final step")
|
249
|
-
final_result = final_step(result_2)
|
250
|
-
```
|