mcp-proxy-adapter 2.1.0__py3-none-any.whl → 2.1.2__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.
- docs/README.md +172 -0
- docs/README_ru.md +172 -0
- docs/architecture.md +251 -0
- docs/architecture_ru.md +343 -0
- docs/command_development.md +250 -0
- docs/command_development_ru.md +593 -0
- docs/deployment.md +251 -0
- docs/deployment_ru.md +1298 -0
- docs/examples.md +254 -0
- docs/examples_ru.md +401 -0
- docs/mcp_proxy_adapter.md +251 -0
- docs/mcp_proxy_adapter_ru.md +405 -0
- docs/quickstart.md +251 -0
- docs/quickstart_ru.md +397 -0
- docs/testing.md +255 -0
- docs/testing_ru.md +469 -0
- docs/validation_ru.md +287 -0
- examples/analyze_config.py +141 -0
- examples/basic_integration.py +161 -0
- examples/docstring_and_schema_example.py +60 -0
- examples/extension_example.py +60 -0
- examples/help_best_practices.py +67 -0
- examples/help_usage.py +64 -0
- examples/mcp_proxy_client.py +131 -0
- examples/mcp_proxy_config.json +175 -0
- examples/openapi_server.py +369 -0
- examples/project_structure_example.py +47 -0
- examples/testing_example.py +53 -0
- mcp_proxy_adapter/__init__.py +17 -0
- mcp_proxy_adapter/adapter.py +697 -0
- mcp_proxy_adapter/models.py +47 -0
- mcp_proxy_adapter/registry.py +439 -0
- mcp_proxy_adapter/schema.py +257 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/METADATA +2 -2
- mcp_proxy_adapter-2.1.2.dist-info/RECORD +61 -0
- mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +5 -0
- scripts/code_analyzer/code_analyzer.py +328 -0
- scripts/code_analyzer/register_commands.py +446 -0
- scripts/publish.py +85 -0
- tests/conftest.py +12 -0
- tests/test_adapter.py +529 -0
- tests/test_adapter_coverage.py +274 -0
- tests/test_basic_dispatcher.py +169 -0
- tests/test_command_registry.py +328 -0
- tests/test_examples.py +32 -0
- tests/test_mcp_proxy_adapter.py +568 -0
- tests/test_mcp_proxy_adapter_basic.py +262 -0
- tests/test_part1.py +348 -0
- tests/test_part2.py +524 -0
- tests/test_schema.py +358 -0
- tests/test_simple_adapter.py +251 -0
- adapters/__init__.py +0 -16
- cli/__init__.py +0 -12
- cli/__main__.py +0 -79
- cli/command_runner.py +0 -233
- generators/__init__.py +0 -14
- generators/endpoint_generator.py +0 -172
- generators/openapi_generator.py +0 -254
- generators/rest_api_generator.py +0 -207
- mcp_proxy_adapter-2.1.0.dist-info/RECORD +0 -28
- mcp_proxy_adapter-2.1.0.dist-info/top_level.txt +0 -7
- openapi_schema/__init__.py +0 -38
- openapi_schema/command_registry.py +0 -312
- openapi_schema/rest_schema.py +0 -510
- openapi_schema/rpc_generator.py +0 -307
- openapi_schema/rpc_schema.py +0 -416
- validators/__init__.py +0 -14
- validators/base_validator.py +0 -23
- {analyzers → mcp_proxy_adapter/analyzers}/__init__.py +0 -0
- {analyzers → mcp_proxy_adapter/analyzers}/docstring_analyzer.py +0 -0
- {analyzers → mcp_proxy_adapter/analyzers}/type_analyzer.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/__init__.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/base_dispatcher.py +0 -0
- {dispatchers → mcp_proxy_adapter/dispatchers}/json_rpc_dispatcher.py +0 -0
- {validators → mcp_proxy_adapter/validators}/docstring_validator.py +0 -0
- {validators → mcp_proxy_adapter/validators}/metadata_validator.py +0 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,446 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Инструмент для автоматической регистрации всех команд с метаданными.
|
4
|
+
Проходит по всем модулям с командами и регистрирует их в диспетчере.
|
5
|
+
"""
|
6
|
+
import os
|
7
|
+
import importlib
|
8
|
+
import inspect
|
9
|
+
import sys
|
10
|
+
from typing import Dict, Any, Optional, List, Tuple, get_type_hints, get_origin, get_args
|
11
|
+
import docstring_parser
|
12
|
+
|
13
|
+
# Добавляем корневую директорию проекта в sys.path
|
14
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
15
|
+
|
16
|
+
from utils.command_dispatcher import dispatcher
|
17
|
+
import commands
|
18
|
+
from commands import get_all_commands, get_command_names
|
19
|
+
|
20
|
+
def map_type_to_openapi(type_hint) -> str:
|
21
|
+
"""
|
22
|
+
Преобразует тип Python в тип OpenAPI.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
type_hint: Тип Python
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
str: Строковое представление типа OpenAPI
|
29
|
+
"""
|
30
|
+
# Проверяем на None
|
31
|
+
if type_hint is None:
|
32
|
+
return "object"
|
33
|
+
|
34
|
+
# Обрабатываем примитивные типы
|
35
|
+
primitives = {
|
36
|
+
str: "string",
|
37
|
+
int: "integer",
|
38
|
+
float: "number",
|
39
|
+
bool: "boolean",
|
40
|
+
list: "array",
|
41
|
+
dict: "object",
|
42
|
+
Any: "object"
|
43
|
+
}
|
44
|
+
|
45
|
+
if type_hint in primitives:
|
46
|
+
return primitives[type_hint]
|
47
|
+
|
48
|
+
# Проверяем на обобщенные типы
|
49
|
+
origin = get_origin(type_hint)
|
50
|
+
if origin is not None:
|
51
|
+
# Обрабатываем List[X], Dict[X, Y] и т.д.
|
52
|
+
if origin in (list, List):
|
53
|
+
return "array"
|
54
|
+
elif origin in (dict, Dict):
|
55
|
+
return "object"
|
56
|
+
elif origin is Union:
|
57
|
+
# Для Union берем первый тип, который не None
|
58
|
+
args = get_args(type_hint)
|
59
|
+
for arg in args:
|
60
|
+
if arg is not type(None):
|
61
|
+
return map_type_to_openapi(arg)
|
62
|
+
return "object"
|
63
|
+
|
64
|
+
# По умолчанию возвращаем object
|
65
|
+
return "object"
|
66
|
+
|
67
|
+
def extract_param_types_from_handler(handler) -> Dict[str, str]:
|
68
|
+
"""
|
69
|
+
Извлекает типы параметров из аннотаций функции.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
handler: Функция-обработчик
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Dict[str, str]: Словарь {имя_параметра: тип_openapi}
|
76
|
+
"""
|
77
|
+
param_types = {}
|
78
|
+
|
79
|
+
try:
|
80
|
+
# Получаем аннотации типов
|
81
|
+
type_hints = get_type_hints(handler)
|
82
|
+
|
83
|
+
# Преобразуем типы Python в типы OpenAPI
|
84
|
+
for param_name, param_type in type_hints.items():
|
85
|
+
# Пропускаем return тип
|
86
|
+
if param_name == 'return':
|
87
|
+
continue
|
88
|
+
|
89
|
+
# Преобразуем тип Python в тип OpenAPI
|
90
|
+
openapi_type = map_type_to_openapi(param_type)
|
91
|
+
param_types[param_name] = openapi_type
|
92
|
+
except Exception as e:
|
93
|
+
print(f" ⚠️ Ошибка при извлечении типов параметров: {str(e)}")
|
94
|
+
|
95
|
+
return param_types
|
96
|
+
|
97
|
+
def validate_docstring(handler, command_name: str) -> Tuple[bool, List[str]]:
|
98
|
+
"""
|
99
|
+
Проверяет соответствие докстринга функции-обработчика её формальным параметрам.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
handler: Функция-обработчик команды
|
103
|
+
command_name: Имя команды
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
Tuple[bool, List[str]]: Флаг валидности и список ошибок
|
107
|
+
"""
|
108
|
+
errors = []
|
109
|
+
|
110
|
+
# Получаем формальные параметры функции
|
111
|
+
try:
|
112
|
+
sig = inspect.signature(handler)
|
113
|
+
formal_params = list(sig.parameters.keys())
|
114
|
+
|
115
|
+
# Пропускаем параметр self для методов
|
116
|
+
if formal_params and formal_params[0] == 'self':
|
117
|
+
formal_params = formal_params[1:]
|
118
|
+
|
119
|
+
# Парсим докстринг
|
120
|
+
docstring = handler.__doc__ or ""
|
121
|
+
parsed_doc = docstring_parser.parse(docstring)
|
122
|
+
|
123
|
+
# Проверяем наличие описания функции
|
124
|
+
if not parsed_doc.short_description and not parsed_doc.long_description:
|
125
|
+
errors.append(f"Отсутствует описание функции {command_name}")
|
126
|
+
|
127
|
+
# Получаем параметры из докстринга
|
128
|
+
doc_params = {param.arg_name: param for param in parsed_doc.params}
|
129
|
+
|
130
|
+
# Проверяем, что все формальные параметры описаны в докстринге
|
131
|
+
for param in formal_params:
|
132
|
+
if param not in doc_params and param != 'params': # 'params' - особый случай, может быть словарём всех параметров
|
133
|
+
errors.append(f"Параметр '{param}' не описан в докстринге функции {command_name}")
|
134
|
+
|
135
|
+
# Проверяем наличие returns в докстринге
|
136
|
+
if not parsed_doc.returns and not any(t.type_name == 'Returns' for t in parsed_doc.meta):
|
137
|
+
errors.append(f"Отсутствует описание возвращаемого значения в докстринге функции {command_name}")
|
138
|
+
|
139
|
+
# Проверяем аннотации типов
|
140
|
+
type_hints = get_type_hints(handler)
|
141
|
+
for param in formal_params:
|
142
|
+
if param not in type_hints and param != 'params':
|
143
|
+
errors.append(f"Отсутствует аннотация типа для параметра '{param}' в функции {command_name}")
|
144
|
+
|
145
|
+
return len(errors) == 0, errors
|
146
|
+
except Exception as e:
|
147
|
+
return False, [f"Ошибка при валидации докстринга: {str(e)}"]
|
148
|
+
|
149
|
+
def extract_metadata_from_command(command_name: str, command_info: Dict[str, Any], handler=None):
|
150
|
+
"""
|
151
|
+
Извлекает метаданные из информации о команде и сигнатуры функции-обработчика.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
command_name: Имя команды
|
155
|
+
command_info: Информация о команде
|
156
|
+
handler: Функция-обработчик (опционально)
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
dict: Метаданные для регистрации команды
|
160
|
+
"""
|
161
|
+
metadata = {
|
162
|
+
"description": command_info.get("description", f"Команда {command_name}"),
|
163
|
+
"summary": command_name.replace("_", " ").title(),
|
164
|
+
"params": {}
|
165
|
+
}
|
166
|
+
|
167
|
+
# Если есть обработчик, извлекаем типы параметров из него
|
168
|
+
param_types_from_handler = {}
|
169
|
+
if handler:
|
170
|
+
param_types_from_handler = extract_param_types_from_handler(handler)
|
171
|
+
|
172
|
+
# Если в команде определены параметры, добавляем их
|
173
|
+
if "parameters" in command_info:
|
174
|
+
for param_name, param_info in command_info["parameters"].items():
|
175
|
+
# Начинаем с типа из метаданных команды
|
176
|
+
param_type = param_info.get("type", "string")
|
177
|
+
|
178
|
+
# Если есть тип из аннотации функции, используем его
|
179
|
+
if param_name in param_types_from_handler:
|
180
|
+
param_type = param_types_from_handler[param_name]
|
181
|
+
|
182
|
+
metadata["params"][param_name] = {
|
183
|
+
"type": param_type,
|
184
|
+
"description": param_info.get("description", f"Параметр {param_name}"),
|
185
|
+
"required": param_info.get("required", False)
|
186
|
+
}
|
187
|
+
|
188
|
+
if "default" in param_info:
|
189
|
+
metadata["params"][param_name]["default"] = param_info["default"]
|
190
|
+
# Если параметры не определены, но есть обработчик, извлекаем их из обработчика
|
191
|
+
elif handler:
|
192
|
+
# Получаем сигнатуру функции
|
193
|
+
sig = inspect.signature(handler)
|
194
|
+
|
195
|
+
# Проходим по параметрам функции
|
196
|
+
for param_name, param in sig.parameters.items():
|
197
|
+
# Пропускаем self для методов
|
198
|
+
if param_name == 'self':
|
199
|
+
continue
|
200
|
+
|
201
|
+
# Если параметр называется params, предполагаем, что это словарь всех параметров
|
202
|
+
if param_name == 'params':
|
203
|
+
# В этом случае метаданные недоступны из сигнатуры
|
204
|
+
continue
|
205
|
+
|
206
|
+
# Определяем, является ли параметр обязательным
|
207
|
+
required = param.default == inspect.Parameter.empty
|
208
|
+
|
209
|
+
# Определяем тип параметра
|
210
|
+
param_type = "string" # По умолчанию
|
211
|
+
if param_name in param_types_from_handler:
|
212
|
+
param_type = param_types_from_handler[param_name]
|
213
|
+
|
214
|
+
# Получаем описание из докстринга
|
215
|
+
description = f"Параметр {param_name}"
|
216
|
+
try:
|
217
|
+
# Парсим докстринг
|
218
|
+
docstring = handler.__doc__ or ""
|
219
|
+
parsed_doc = docstring_parser.parse(docstring)
|
220
|
+
|
221
|
+
# Ищем описание параметра
|
222
|
+
for doc_param in parsed_doc.params:
|
223
|
+
if doc_param.arg_name == param_name:
|
224
|
+
description = doc_param.description or description
|
225
|
+
break
|
226
|
+
except Exception:
|
227
|
+
pass
|
228
|
+
|
229
|
+
# Добавляем параметр в метаданные
|
230
|
+
metadata["params"][param_name] = {
|
231
|
+
"type": param_type,
|
232
|
+
"description": description,
|
233
|
+
"required": required
|
234
|
+
}
|
235
|
+
|
236
|
+
# Добавляем значение по умолчанию, если есть
|
237
|
+
if param.default != inspect.Parameter.empty:
|
238
|
+
metadata["params"][param_name]["default"] = param.default
|
239
|
+
|
240
|
+
return metadata
|
241
|
+
|
242
|
+
def validate_handler_metadata(handler, command_name: str, command_info: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
243
|
+
"""
|
244
|
+
Проверяет соответствие метаданных команды формальным параметрам функции-обработчика.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
handler: Функция-обработчик команды
|
248
|
+
command_name: Имя команды
|
249
|
+
command_info: Информация о команде
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
Tuple[bool, List[str]]: Флаг валидности и список ошибок
|
253
|
+
"""
|
254
|
+
errors = []
|
255
|
+
|
256
|
+
# Получаем формальные параметры функции
|
257
|
+
sig = inspect.signature(handler)
|
258
|
+
formal_params = list(sig.parameters.keys())
|
259
|
+
|
260
|
+
# Пропускаем параметр self для методов
|
261
|
+
if formal_params and formal_params[0] == 'self':
|
262
|
+
formal_params = formal_params[1:]
|
263
|
+
|
264
|
+
# Если функция принимает просто словарь params, пропускаем проверку
|
265
|
+
if len(formal_params) == 1 and formal_params[0] == 'params':
|
266
|
+
return True, []
|
267
|
+
|
268
|
+
# Получаем типы параметров из аннотаций функции
|
269
|
+
param_types = extract_param_types_from_handler(handler)
|
270
|
+
|
271
|
+
# Проверяем соответствие метаданных команды формальным параметрам
|
272
|
+
if "parameters" in command_info:
|
273
|
+
for param_name, param_info in command_info["parameters"].items():
|
274
|
+
# Проверяем наличие параметра в сигнатуре функции
|
275
|
+
if param_name not in formal_params:
|
276
|
+
errors.append(f"Параметр '{param_name}' указан в метаданных, но отсутствует в сигнатуре функции {command_name}")
|
277
|
+
continue
|
278
|
+
|
279
|
+
# Проверяем соответствие типов
|
280
|
+
if param_name in param_types and "type" in param_info:
|
281
|
+
openapi_type = param_info["type"]
|
282
|
+
annotation_type = param_types[param_name]
|
283
|
+
|
284
|
+
if openapi_type != annotation_type:
|
285
|
+
errors.append(f"Тип параметра '{param_name}' в метаданных ({openapi_type}) не соответствует аннотации функции ({annotation_type})")
|
286
|
+
|
287
|
+
# Проверяем, что все обязательные формальные параметры указаны в метаданных
|
288
|
+
for param_name, param in sig.parameters.items():
|
289
|
+
if param_name != 'self' and param.default == inspect.Parameter.empty:
|
290
|
+
if param_name not in command_info.get("parameters", {}):
|
291
|
+
errors.append(f"Обязательный параметр '{param_name}' не указан в метаданных команды {command_name}")
|
292
|
+
|
293
|
+
return len(errors) == 0, errors
|
294
|
+
|
295
|
+
def find_handler_function(command_name: str):
|
296
|
+
"""
|
297
|
+
Находит функцию-обработчик для команды.
|
298
|
+
|
299
|
+
Args:
|
300
|
+
command_name: Имя команды
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
function: Функция-обработчик команды или None, если не найдена
|
304
|
+
"""
|
305
|
+
# Проверяем различные варианты имени функции
|
306
|
+
function_names = [
|
307
|
+
command_name, # имя_команды
|
308
|
+
f"{command_name}_command", # имя_команды_command
|
309
|
+
f"execute_{command_name}", # execute_имя_команды
|
310
|
+
"execute" # execute (общий обработчик)
|
311
|
+
]
|
312
|
+
|
313
|
+
# Пути для поиска функций
|
314
|
+
command_paths = [
|
315
|
+
f"commands.metadata.{command_name}",
|
316
|
+
f"commands.search.{command_name}",
|
317
|
+
f"commands.index.{command_name}",
|
318
|
+
f"handlers.{command_name}_handlers",
|
319
|
+
f"rpc.handlers"
|
320
|
+
]
|
321
|
+
|
322
|
+
# Пытаемся импортировать модули и найти функцию
|
323
|
+
for path in command_paths:
|
324
|
+
try:
|
325
|
+
module = importlib.import_module(path)
|
326
|
+
|
327
|
+
for func_name in function_names:
|
328
|
+
if hasattr(module, func_name):
|
329
|
+
return getattr(module, func_name)
|
330
|
+
except (ImportError, ModuleNotFoundError):
|
331
|
+
continue
|
332
|
+
|
333
|
+
return None
|
334
|
+
|
335
|
+
def register_command(command_name: str, command_info: Dict[str, Any], strict: bool = True, auto_fix: bool = False):
|
336
|
+
"""
|
337
|
+
Регистрирует команду в диспетчере с метаданными.
|
338
|
+
|
339
|
+
Args:
|
340
|
+
command_name: Имя команды
|
341
|
+
command_info: Информация о команде
|
342
|
+
strict: Если True, прерывает регистрацию при обнаружении ошибок
|
343
|
+
auto_fix: Если True, пытается автоматически исправить несоответствия
|
344
|
+
"""
|
345
|
+
# Находим функцию-обработчик
|
346
|
+
handler = find_handler_function(command_name)
|
347
|
+
|
348
|
+
if not handler:
|
349
|
+
print(f"⚠️ Не удалось найти обработчик для команды '{command_name}'")
|
350
|
+
return False
|
351
|
+
|
352
|
+
# Проверяем соответствие документации и формальных параметров
|
353
|
+
is_valid_doc, doc_errors = validate_docstring(handler, command_name)
|
354
|
+
is_valid_meta, meta_errors = validate_handler_metadata(handler, command_name, command_info)
|
355
|
+
|
356
|
+
# Выводим все ошибки
|
357
|
+
if not is_valid_doc or not is_valid_meta:
|
358
|
+
print(f"🚫 Ошибки в команде '{command_name}':")
|
359
|
+
|
360
|
+
for error in doc_errors:
|
361
|
+
print(f" - {error}")
|
362
|
+
|
363
|
+
for error in meta_errors:
|
364
|
+
print(f" - {error}")
|
365
|
+
|
366
|
+
# В строгом режиме без автоисправления пропускаем регистрацию
|
367
|
+
if strict and not auto_fix:
|
368
|
+
print(f"❌ Регистрация команды '{command_name}' пропущена из-за ошибок")
|
369
|
+
return False
|
370
|
+
|
371
|
+
# Извлекаем метаданные, учитывая аннотации функции
|
372
|
+
metadata = extract_metadata_from_command(command_name, command_info, handler if auto_fix else None)
|
373
|
+
|
374
|
+
# Регистрируем команду в диспетчере
|
375
|
+
print(f"📝 Регистрируем команду '{command_name}'")
|
376
|
+
dispatcher.register_handler(
|
377
|
+
command=command_name,
|
378
|
+
handler=handler,
|
379
|
+
description=metadata["description"],
|
380
|
+
summary=metadata["summary"],
|
381
|
+
params=metadata["params"]
|
382
|
+
)
|
383
|
+
|
384
|
+
return True
|
385
|
+
|
386
|
+
def register_all_commands(strict: bool = True, auto_fix: bool = False):
|
387
|
+
"""
|
388
|
+
Регистрирует все команды в диспетчере.
|
389
|
+
|
390
|
+
Args:
|
391
|
+
strict: Если True, прерывает регистрацию команды при обнаружении ошибок
|
392
|
+
auto_fix: Если True, пытается автоматически исправить несоответствия
|
393
|
+
"""
|
394
|
+
# Получаем все команды
|
395
|
+
commands_info = get_all_commands()
|
396
|
+
|
397
|
+
# Счетчики
|
398
|
+
successful = 0
|
399
|
+
failed = 0
|
400
|
+
skipped = 0
|
401
|
+
|
402
|
+
# Регистрируем каждую команду
|
403
|
+
for command_name, command_info in commands_info.items():
|
404
|
+
# Пропускаем команду help, она уже зарегистрирована
|
405
|
+
if command_name == "help":
|
406
|
+
skipped += 1
|
407
|
+
continue
|
408
|
+
|
409
|
+
if register_command(command_name, command_info, strict, auto_fix):
|
410
|
+
successful += 1
|
411
|
+
else:
|
412
|
+
failed += 1
|
413
|
+
|
414
|
+
print(f"\n✅ Итоги регистрации команд:")
|
415
|
+
print(f" - Успешно: {successful}")
|
416
|
+
print(f" - С ошибками: {failed}")
|
417
|
+
print(f" - Пропущено: {skipped}")
|
418
|
+
print(f" - Всего в диспетчере: {len(dispatcher.get_valid_commands())}")
|
419
|
+
|
420
|
+
if failed > 0 and strict:
|
421
|
+
print("\n⚠️ ВНИМАНИЕ: Некоторые команды не были зарегистрированы из-за ошибок")
|
422
|
+
print(" Используйте register_all_commands(strict=False) для регистрации всех команд")
|
423
|
+
print(" Или register_all_commands(auto_fix=True) для автоматического исправления ошибок")
|
424
|
+
|
425
|
+
if __name__ == "__main__":
|
426
|
+
print("🔍 Начинаем регистрацию команд...")
|
427
|
+
|
428
|
+
# Проверяем аргументы командной строки
|
429
|
+
strict_mode = True
|
430
|
+
auto_fix_mode = False
|
431
|
+
|
432
|
+
# Обрабатываем аргументы командной строки
|
433
|
+
if len(sys.argv) > 1:
|
434
|
+
if "--no-strict" in sys.argv:
|
435
|
+
strict_mode = False
|
436
|
+
print("⚙️ Запуск в нестрогом режиме: команды с ошибками будут зарегистрированы")
|
437
|
+
|
438
|
+
if "--auto-fix" in sys.argv:
|
439
|
+
auto_fix_mode = True
|
440
|
+
print("⚙️ Режим автоисправления: типы и описания будут извлечены из аннотаций функций")
|
441
|
+
else:
|
442
|
+
print("⚙️ Запуск в строгом режиме: команды с ошибками не будут зарегистрированы")
|
443
|
+
print(" Используйте --no-strict для отключения строгого режима")
|
444
|
+
print(" Используйте --auto-fix для автоматического исправления ошибок")
|
445
|
+
|
446
|
+
register_all_commands(strict=strict_mode, auto_fix=auto_fix_mode)
|
scripts/publish.py
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Скрипт для публикации пакета в PyPI.
|
4
|
+
"""
|
5
|
+
import sys
|
6
|
+
import os
|
7
|
+
import subprocess
|
8
|
+
import shutil
|
9
|
+
import argparse
|
10
|
+
|
11
|
+
# Путь к корню проекта
|
12
|
+
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
13
|
+
|
14
|
+
|
15
|
+
def clean_build_dirs():
|
16
|
+
"""Очищает директории сборки."""
|
17
|
+
print("Очистка директорий сборки...")
|
18
|
+
|
19
|
+
dirs_to_clean = [
|
20
|
+
os.path.join(PROJECT_ROOT, 'build'),
|
21
|
+
os.path.join(PROJECT_ROOT, 'dist'),
|
22
|
+
os.path.join(PROJECT_ROOT, 'mcp_proxy_adapter.egg-info'),
|
23
|
+
os.path.join(PROJECT_ROOT, 'src/mcp_proxy_adapter.egg-info')
|
24
|
+
]
|
25
|
+
|
26
|
+
for dir_path in dirs_to_clean:
|
27
|
+
if os.path.exists(dir_path):
|
28
|
+
print(f"Удаление {dir_path}")
|
29
|
+
shutil.rmtree(dir_path)
|
30
|
+
|
31
|
+
|
32
|
+
def build_package():
|
33
|
+
"""Собирает пакет."""
|
34
|
+
print("Сборка пакета...")
|
35
|
+
|
36
|
+
# Сборка колеса
|
37
|
+
subprocess.run([sys.executable, "-m", "build", "--wheel", "--sdist"], cwd=PROJECT_ROOT, check=True)
|
38
|
+
|
39
|
+
|
40
|
+
def upload_to_pypi(test=True):
|
41
|
+
"""Загружает пакет в PyPI."""
|
42
|
+
if test:
|
43
|
+
print("Загрузка в TestPyPI...")
|
44
|
+
cmd = [
|
45
|
+
sys.executable, "-m", "twine", "upload",
|
46
|
+
"--repository-url", "https://test.pypi.org/legacy/",
|
47
|
+
"dist/*"
|
48
|
+
]
|
49
|
+
else:
|
50
|
+
print("Загрузка в PyPI...")
|
51
|
+
cmd = [sys.executable, "-m", "twine", "upload", "dist/*"]
|
52
|
+
|
53
|
+
subprocess.run(cmd, cwd=PROJECT_ROOT, check=True)
|
54
|
+
|
55
|
+
|
56
|
+
def main():
|
57
|
+
parser = argparse.ArgumentParser(description="Публикация пакета в PyPI")
|
58
|
+
parser.add_argument('--test', action='store_true', help='Загрузить в TestPyPI вместо основного PyPI')
|
59
|
+
parser.add_argument('--no-clean', action='store_true', help='Не очищать директории сборки')
|
60
|
+
parser.add_argument('--build-only', action='store_true', help='Только сборка без загрузки')
|
61
|
+
|
62
|
+
args = parser.parse_args()
|
63
|
+
|
64
|
+
try:
|
65
|
+
if not args.no_clean:
|
66
|
+
clean_build_dirs()
|
67
|
+
|
68
|
+
build_package()
|
69
|
+
|
70
|
+
if not args.build_only:
|
71
|
+
upload_to_pypi(test=args.test)
|
72
|
+
|
73
|
+
print("Готово!")
|
74
|
+
except subprocess.CalledProcessError as e:
|
75
|
+
print(f"Ошибка при выполнении команды: {e}")
|
76
|
+
return 1
|
77
|
+
except Exception as e:
|
78
|
+
print(f"Ошибка: {e}")
|
79
|
+
return 1
|
80
|
+
|
81
|
+
return 0
|
82
|
+
|
83
|
+
|
84
|
+
if __name__ == "__main__":
|
85
|
+
sys.exit(main())
|
tests/conftest.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
import warnings
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
# Отключаем все DeprecationWarning и устаревшие предупреждения для чистоты тестов
|
5
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
6
|
+
warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
|
7
|
+
|
8
|
+
# Для pytest >=7.1 можно использовать mark.filterwarnings
|
9
|
+
pytestmark = pytest.mark.filterwarnings(
|
10
|
+
"ignore::DeprecationWarning",
|
11
|
+
"ignore::PendingDeprecationWarning"
|
12
|
+
)
|