mcp-proxy-adapter 2.1.17__py3-none-any.whl → 3.0.0__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.
- examples/__init__.py +19 -0
- examples/anti_patterns/README.md +51 -0
- examples/anti_patterns/__init__.py +9 -0
- examples/anti_patterns/bad_design/README.md +72 -0
- examples/anti_patterns/bad_design/global_state.py +170 -0
- examples/anti_patterns/bad_design/monolithic_command.py +272 -0
- examples/basic_example/README.md +131 -0
- examples/basic_example/__init__.py +8 -0
- examples/basic_example/commands/__init__.py +5 -0
- examples/basic_example/commands/echo_command.py +95 -0
- examples/basic_example/commands/math_command.py +151 -0
- examples/basic_example/commands/time_command.py +152 -0
- examples/basic_example/config.json +21 -0
- examples/basic_example/config.yaml +20 -0
- examples/basic_example/docs/EN/README.md +136 -0
- examples/basic_example/docs/RU/README.md +136 -0
- examples/basic_example/main.py +50 -0
- examples/basic_example/server.py +45 -0
- examples/basic_example/tests/conftest.py +243 -0
- examples/commands/echo_command.py +52 -0
- examples/commands/echo_result.py +65 -0
- examples/commands/get_date_command.py +98 -0
- examples/commands/new_uuid4_command.py +91 -0
- examples/complete_example/Dockerfile +24 -0
- examples/complete_example/README.md +92 -0
- examples/complete_example/__init__.py +8 -0
- examples/complete_example/commands/__init__.py +5 -0
- examples/complete_example/commands/system_command.py +327 -0
- examples/complete_example/config.json +41 -0
- examples/complete_example/configs/config.dev.yaml +40 -0
- examples/complete_example/configs/config.docker.yaml +40 -0
- examples/complete_example/docker-compose.yml +35 -0
- examples/complete_example/main.py +67 -0
- examples/complete_example/requirements.txt +20 -0
- examples/complete_example/server.py +85 -0
- examples/minimal_example/README.md +51 -0
- examples/minimal_example/__init__.py +8 -0
- examples/minimal_example/config.json +21 -0
- examples/minimal_example/config.yaml +26 -0
- examples/minimal_example/main.py +67 -0
- examples/minimal_example/simple_server.py +124 -0
- examples/minimal_example/tests/conftest.py +171 -0
- examples/minimal_example/tests/test_hello_command.py +111 -0
- examples/minimal_example/tests/test_integration.py +183 -0
- examples/server.py +69 -0
- examples/simple_server.py +137 -0
- examples/test_server.py +126 -0
- mcp_proxy_adapter/__init__.py +33 -1
- mcp_proxy_adapter/config.py +186 -0
- mcp_proxy_adapter/custom_openapi.py +125 -0
- mcp_proxy_adapter/framework.py +109 -0
- mcp_proxy_adapter/openapi.py +403 -0
- mcp_proxy_adapter/version.py +3 -0
- mcp_proxy_adapter-3.0.0.dist-info/METADATA +200 -0
- mcp_proxy_adapter-3.0.0.dist-info/RECORD +58 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/top_level.txt +1 -0
- mcp_proxy_adapter/adapter.py +0 -697
- mcp_proxy_adapter/analyzers/__init__.py +0 -1
- mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
- mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
- mcp_proxy_adapter/dispatchers/__init__.py +0 -1
- mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
- mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -262
- mcp_proxy_adapter/examples/analyze_config.py +0 -141
- mcp_proxy_adapter/examples/basic_integration.py +0 -155
- mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
- mcp_proxy_adapter/examples/extension_example.py +0 -72
- mcp_proxy_adapter/examples/help_best_practices.py +0 -67
- mcp_proxy_adapter/examples/help_usage.py +0 -64
- mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
- mcp_proxy_adapter/examples/openapi_server.py +0 -383
- mcp_proxy_adapter/examples/project_structure_example.py +0 -47
- mcp_proxy_adapter/examples/testing_example.py +0 -64
- mcp_proxy_adapter/models.py +0 -47
- mcp_proxy_adapter/registry.py +0 -439
- mcp_proxy_adapter/schema.py +0 -257
- mcp_proxy_adapter/testing_utils.py +0 -112
- mcp_proxy_adapter/validators/__init__.py +0 -1
- mcp_proxy_adapter/validators/docstring_validator.py +0 -75
- mcp_proxy_adapter/validators/metadata_validator.py +0 -76
- mcp_proxy_adapter-2.1.17.dist-info/METADATA +0 -376
- mcp_proxy_adapter-2.1.17.dist-info/RECORD +0 -30
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,64 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Testing Example for MCPProxyAdapter
|
3
|
-
|
4
|
-
- How to write unit and integration tests for commands
|
5
|
-
- How to test help and error handling
|
6
|
-
- Best practices for test structure
|
7
|
-
|
8
|
-
Run:
|
9
|
-
python examples/testing_example.py
|
10
|
-
"""
|
11
|
-
import os
|
12
|
-
import sys
|
13
|
-
import asyncio
|
14
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
15
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
16
|
-
|
17
|
-
class MyRegistry:
|
18
|
-
def __init__(self):
|
19
|
-
self.dispatcher = self
|
20
|
-
self.commands = {"echo": self.echo}
|
21
|
-
self.commands_info = {"echo": {"description": "Echo input string", "params": {"text": {"type": "string", "description": "Text to echo", "required": True}}}}
|
22
|
-
def get_valid_commands(self):
|
23
|
-
return list(self.commands.keys())
|
24
|
-
def get_command_info(self, command):
|
25
|
-
return self.commands_info.get(command)
|
26
|
-
def get_commands_info(self):
|
27
|
-
return self.commands_info
|
28
|
-
def execute(self, command, **params):
|
29
|
-
if command == "echo":
|
30
|
-
return self.echo(**params)
|
31
|
-
raise KeyError(f"Unknown command: {command}")
|
32
|
-
def add_generator(self, generator):
|
33
|
-
pass
|
34
|
-
def echo(self, text: str) -> str:
|
35
|
-
"""Echo input string."""
|
36
|
-
return text
|
37
|
-
|
38
|
-
def test_echo():
|
39
|
-
registry = MyRegistry()
|
40
|
-
adapter = MCPProxyAdapter(registry)
|
41
|
-
# Unit test
|
42
|
-
assert registry.execute("echo", text="hi") == "hi"
|
43
|
-
# Integration test (simulate JSON-RPC)
|
44
|
-
class Request:
|
45
|
-
method = "echo"
|
46
|
-
params = {"text": "hello"}
|
47
|
-
id = 1
|
48
|
-
response = adapter.router.routes[0].endpoint(Request())
|
49
|
-
# Not a real FastAPI call, just for illustration
|
50
|
-
print("[TEST] Echo command passed.")
|
51
|
-
|
52
|
-
# Call sync handler
|
53
|
-
registry = MyRegistry()
|
54
|
-
adapter = MCPProxyAdapter(registry)
|
55
|
-
result_sync = registry.execute('echo', text='hi')
|
56
|
-
print(result_sync) # hi
|
57
|
-
|
58
|
-
# Call async handler
|
59
|
-
result_async = asyncio.run(registry.execute('async', x=10))
|
60
|
-
print(result_async) # 20
|
61
|
-
|
62
|
-
if __name__ == "__main__":
|
63
|
-
test_echo()
|
64
|
-
print("All tests passed.")
|
mcp_proxy_adapter/models.py
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Data models for MCP Proxy Adapter.
|
3
|
-
"""
|
4
|
-
from typing import Dict, Any, List, Optional, Union
|
5
|
-
from pydantic import BaseModel, Field
|
6
|
-
|
7
|
-
class JsonRpcRequest(BaseModel):
|
8
|
-
"""Base model for JSON-RPC requests."""
|
9
|
-
jsonrpc: str = Field(default="2.0", description="JSON-RPC version")
|
10
|
-
method: str = Field(..., description="Method name to call")
|
11
|
-
params: Dict[str, Any] = Field(default_factory=dict, description="Method parameters")
|
12
|
-
id: Optional[Union[str, int]] = Field(default=None, description="Request identifier")
|
13
|
-
|
14
|
-
class JsonRpcResponse(BaseModel):
|
15
|
-
"""Base model for JSON-RPC responses."""
|
16
|
-
jsonrpc: str = Field(default="2.0", description="JSON-RPC version")
|
17
|
-
result: Optional[Any] = Field(default=None, description="Method execution result")
|
18
|
-
error: Optional[Dict[str, Any]] = Field(default=None, description="Error information")
|
19
|
-
id: Optional[Union[str, int]] = Field(default=None, description="Request identifier")
|
20
|
-
|
21
|
-
class CommandInfo(BaseModel):
|
22
|
-
"""Command information model."""
|
23
|
-
name: str = Field(..., description="Command name")
|
24
|
-
description: str = Field(default="", description="Command description")
|
25
|
-
summary: Optional[str] = Field(default=None, description="Brief description")
|
26
|
-
parameters: Dict[str, Dict[str, Any]] = Field(default_factory=dict, description="Command parameters")
|
27
|
-
returns: Optional[Dict[str, Any]] = Field(default=None, description="Return value information")
|
28
|
-
|
29
|
-
class CommandParameter(BaseModel):
|
30
|
-
"""Command parameter model."""
|
31
|
-
type: str = Field(..., description="Parameter type")
|
32
|
-
description: str = Field(default="", description="Parameter description")
|
33
|
-
required: bool = Field(default=False, description="Whether the parameter is required")
|
34
|
-
default: Optional[Any] = Field(default=None, description="Default value")
|
35
|
-
enum: Optional[List[Any]] = Field(default=None, description="Possible values for enumeration")
|
36
|
-
|
37
|
-
class MCPProxyTool(BaseModel):
|
38
|
-
"""Tool model for MCPProxy."""
|
39
|
-
name: str = Field(..., description="Tool name")
|
40
|
-
description: str = Field(default="", description="Tool description")
|
41
|
-
parameters: Dict[str, Any] = Field(..., description="Tool parameters schema")
|
42
|
-
|
43
|
-
class MCPProxyConfig(BaseModel):
|
44
|
-
"""Configuration model for MCPProxy."""
|
45
|
-
version: str = Field(default="1.0", description="Configuration version")
|
46
|
-
tools: List[MCPProxyTool] = Field(default_factory=list, description="List of tools")
|
47
|
-
routes: List[Dict[str, Any]] = Field(default_factory=list, description="Routes configuration")
|
mcp_proxy_adapter/registry.py
DELETED
@@ -1,439 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Main CommandRegistry class for registering and managing commands.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import importlib
|
6
|
-
import inspect
|
7
|
-
import pkgutil
|
8
|
-
import logging
|
9
|
-
from typing import Dict, Any, Optional, List, Callable, Union, Type, Set, Tuple
|
10
|
-
import docstring_parser
|
11
|
-
|
12
|
-
from .dispatchers.base_dispatcher import BaseDispatcher
|
13
|
-
from .dispatchers.json_rpc_dispatcher import JsonRpcDispatcher
|
14
|
-
from .analyzers.type_analyzer import TypeAnalyzer
|
15
|
-
from .analyzers.docstring_analyzer import DocstringAnalyzer
|
16
|
-
from .validators.docstring_validator import DocstringValidator
|
17
|
-
from .validators.metadata_validator import MetadataValidator
|
18
|
-
|
19
|
-
logger = logging.getLogger("command_registry")
|
20
|
-
|
21
|
-
class CommandRegistry:
|
22
|
-
"""
|
23
|
-
Main class for registering and managing commands.
|
24
|
-
|
25
|
-
CommandRegistry provides an interface for analyzing, validating, and registering
|
26
|
-
commands based on their docstrings and type annotations. It also manages
|
27
|
-
API generators and command dispatchers.
|
28
|
-
"""
|
29
|
-
|
30
|
-
def __init__(
|
31
|
-
self,
|
32
|
-
dispatcher: Optional[BaseDispatcher] = None,
|
33
|
-
strict: bool = True,
|
34
|
-
auto_fix: bool = False
|
35
|
-
):
|
36
|
-
"""
|
37
|
-
Initializes the command registry.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
dispatcher: Command dispatcher for registering handlers
|
41
|
-
strict: If True, stops registration when errors are detected
|
42
|
-
auto_fix: If True, tries to automatically fix inconsistencies
|
43
|
-
"""
|
44
|
-
# Use the specified dispatcher or create a default JSON-RPC dispatcher
|
45
|
-
self.dispatcher = dispatcher or JsonRpcDispatcher()
|
46
|
-
|
47
|
-
# Operation modes
|
48
|
-
self.strict = strict
|
49
|
-
self.auto_fix = auto_fix
|
50
|
-
|
51
|
-
# Analyzers for extracting metadata
|
52
|
-
self._analyzers = []
|
53
|
-
|
54
|
-
# Validators for checking metadata consistency
|
55
|
-
self._validators = []
|
56
|
-
|
57
|
-
# API generators
|
58
|
-
self._generators = []
|
59
|
-
|
60
|
-
# Command information
|
61
|
-
self._commands_info = {}
|
62
|
-
|
63
|
-
# Paths for finding commands
|
64
|
-
self._module_paths = []
|
65
|
-
|
66
|
-
# Add standard analyzers
|
67
|
-
self.add_analyzer(TypeAnalyzer())
|
68
|
-
self.add_analyzer(DocstringAnalyzer())
|
69
|
-
|
70
|
-
# Add standard validators
|
71
|
-
self.add_validator(DocstringValidator())
|
72
|
-
self.add_validator(MetadataValidator())
|
73
|
-
|
74
|
-
def add_analyzer(self, analyzer) -> None:
|
75
|
-
"""
|
76
|
-
Adds a metadata analyzer.
|
77
|
-
|
78
|
-
Args:
|
79
|
-
analyzer: Analyzer object with an analyze method
|
80
|
-
"""
|
81
|
-
self._analyzers.append(analyzer)
|
82
|
-
|
83
|
-
def add_validator(self, validator) -> None:
|
84
|
-
"""
|
85
|
-
Adds a metadata validator.
|
86
|
-
|
87
|
-
Args:
|
88
|
-
validator: Validator object with a validate method
|
89
|
-
"""
|
90
|
-
self._validators.append(validator)
|
91
|
-
|
92
|
-
def add_generator(self, generator) -> None:
|
93
|
-
"""
|
94
|
-
Adds an API generator.
|
95
|
-
|
96
|
-
Args:
|
97
|
-
generator: Generator object with set_dispatcher and generate_* methods
|
98
|
-
"""
|
99
|
-
generator.set_dispatcher(self.dispatcher)
|
100
|
-
self._generators.append(generator)
|
101
|
-
|
102
|
-
def scan_modules(self, module_paths: List[str]) -> None:
|
103
|
-
"""
|
104
|
-
Sets paths for searching modules with commands.
|
105
|
-
|
106
|
-
Args:
|
107
|
-
module_paths: List of module paths
|
108
|
-
"""
|
109
|
-
self._module_paths = module_paths
|
110
|
-
|
111
|
-
def find_command_handlers(self) -> Dict[str, Callable]:
|
112
|
-
"""
|
113
|
-
Searches for command handler functions in the specified modules.
|
114
|
-
|
115
|
-
Returns:
|
116
|
-
Dict[str, Callable]: Dictionary {command_name: handler_function}
|
117
|
-
"""
|
118
|
-
handlers = {}
|
119
|
-
|
120
|
-
# If no search paths are specified, return an empty dictionary
|
121
|
-
if not self._module_paths:
|
122
|
-
return handlers
|
123
|
-
|
124
|
-
# Search for handlers in each module
|
125
|
-
for module_path in self._module_paths:
|
126
|
-
try:
|
127
|
-
module = importlib.import_module(module_path)
|
128
|
-
|
129
|
-
# If this is a package, search in all its modules
|
130
|
-
if hasattr(module, "__path__"):
|
131
|
-
for _, name, is_pkg in pkgutil.iter_modules(module.__path__):
|
132
|
-
# Full submodule name
|
133
|
-
submodule_path = f"{module_path}.{name}"
|
134
|
-
|
135
|
-
# Load the submodule
|
136
|
-
try:
|
137
|
-
submodule = importlib.import_module(submodule_path)
|
138
|
-
|
139
|
-
# Search for handlers in the submodule
|
140
|
-
for handler_name, handler in self._find_handlers_in_module(submodule):
|
141
|
-
handlers[handler_name] = handler
|
142
|
-
except ImportError as e:
|
143
|
-
logger.warning(f"Failed to load submodule {submodule_path}: {str(e)}")
|
144
|
-
|
145
|
-
# Search for handlers in the module itself
|
146
|
-
for handler_name, handler in self._find_handlers_in_module(module):
|
147
|
-
handlers[handler_name] = handler
|
148
|
-
except ImportError as e:
|
149
|
-
logger.warning(f"Failed to load module {module_path}: {str(e)}")
|
150
|
-
|
151
|
-
return handlers
|
152
|
-
|
153
|
-
def _find_handlers_in_module(self, module) -> List[Tuple[str, Callable]]:
|
154
|
-
"""
|
155
|
-
Searches for command handler functions in a module.
|
156
|
-
|
157
|
-
Args:
|
158
|
-
module: Loaded module
|
159
|
-
|
160
|
-
Returns:
|
161
|
-
List[Tuple[str, Callable]]: List of pairs (command_name, handler_function)
|
162
|
-
"""
|
163
|
-
result = []
|
164
|
-
|
165
|
-
# Get all module attributes
|
166
|
-
for name in dir(module):
|
167
|
-
# Skip private attributes
|
168
|
-
if name.startswith("_"):
|
169
|
-
continue
|
170
|
-
|
171
|
-
# Get the attribute
|
172
|
-
attr = getattr(module, name)
|
173
|
-
|
174
|
-
# Check that it's a function or method
|
175
|
-
if callable(attr) and (inspect.isfunction(attr) or inspect.ismethod(attr)):
|
176
|
-
# Check if the function is a command handler
|
177
|
-
command_name = self._get_command_name_from_handler(attr, name)
|
178
|
-
|
179
|
-
if command_name:
|
180
|
-
result.append((command_name, attr))
|
181
|
-
|
182
|
-
return result
|
183
|
-
|
184
|
-
def _get_command_name_from_handler(self, handler: Callable, handler_name: str) -> Optional[str]:
|
185
|
-
"""
|
186
|
-
Determines the command name based on the function name or decorator.
|
187
|
-
|
188
|
-
Args:
|
189
|
-
handler: Handler function
|
190
|
-
handler_name: Function name
|
191
|
-
|
192
|
-
Returns:
|
193
|
-
Optional[str]: Command name or None if the function is not a handler
|
194
|
-
"""
|
195
|
-
# Check if the function has a command_name attribute (set by a decorator)
|
196
|
-
if hasattr(handler, "command_name"):
|
197
|
-
return handler.command_name
|
198
|
-
|
199
|
-
# Check handler name patterns
|
200
|
-
if handler_name.endswith("_command"):
|
201
|
-
# Command name without the _command suffix
|
202
|
-
return handler_name[:-8]
|
203
|
-
|
204
|
-
if handler_name.startswith("execute_"):
|
205
|
-
# Command name without the execute_ prefix
|
206
|
-
return handler_name[8:]
|
207
|
-
|
208
|
-
if handler_name == "execute":
|
209
|
-
# The execute handler can process any command
|
210
|
-
# In this case, the command name is determined by the module name
|
211
|
-
module_name = handler.__module__.split(".")[-1]
|
212
|
-
if module_name != "execute":
|
213
|
-
return module_name
|
214
|
-
|
215
|
-
# Check if the function has a docstring
|
216
|
-
if handler.__doc__:
|
217
|
-
try:
|
218
|
-
# Parse the docstring
|
219
|
-
parsed_doc = docstring_parser.parse(handler.__doc__)
|
220
|
-
|
221
|
-
# Check if the docstring explicitly specifies a command name
|
222
|
-
for meta in parsed_doc.meta:
|
223
|
-
if meta.type_name == "command":
|
224
|
-
return meta.description
|
225
|
-
except Exception:
|
226
|
-
pass
|
227
|
-
|
228
|
-
# By default, use the function name as the command name
|
229
|
-
return handler_name
|
230
|
-
|
231
|
-
def analyze_handler(self, command_name: str, handler: Callable) -> Dict[str, Any]:
|
232
|
-
"""
|
233
|
-
Analyzes the handler function and extracts metadata.
|
234
|
-
|
235
|
-
Args:
|
236
|
-
command_name: Command name
|
237
|
-
handler: Handler function
|
238
|
-
|
239
|
-
Returns:
|
240
|
-
Dict[str, Any]: Command metadata
|
241
|
-
"""
|
242
|
-
# Base metadata
|
243
|
-
metadata = {
|
244
|
-
"description": "",
|
245
|
-
"summary": "",
|
246
|
-
"parameters": {}
|
247
|
-
}
|
248
|
-
|
249
|
-
# Apply all analyzers
|
250
|
-
for analyzer in self._analyzers:
|
251
|
-
try:
|
252
|
-
# Get metadata from the analyzer
|
253
|
-
analyzer_metadata = analyzer.analyze(handler)
|
254
|
-
|
255
|
-
# Merge metadata
|
256
|
-
for key, value in analyzer_metadata.items():
|
257
|
-
if key == "parameters" and metadata.get(key):
|
258
|
-
# Merge parameter information
|
259
|
-
for param_name, param_info in value.items():
|
260
|
-
if param_name in metadata[key]:
|
261
|
-
# Update existing parameter
|
262
|
-
metadata[key][param_name].update(param_info)
|
263
|
-
else:
|
264
|
-
# Add new parameter
|
265
|
-
metadata[key][param_name] = param_info
|
266
|
-
else:
|
267
|
-
# For other keys, simply replace the value
|
268
|
-
metadata[key] = value
|
269
|
-
except Exception as e:
|
270
|
-
logger.warning(f"Error analyzing command '{command_name}' with analyzer {analyzer.__class__.__name__}: {str(e)}")
|
271
|
-
|
272
|
-
# In strict mode, propagate the exception
|
273
|
-
if self.strict:
|
274
|
-
raise
|
275
|
-
|
276
|
-
return metadata
|
277
|
-
|
278
|
-
def validate_handler(self, command_name: str, handler: Callable, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
279
|
-
"""
|
280
|
-
Validates the correspondence between the handler function and metadata.
|
281
|
-
|
282
|
-
Args:
|
283
|
-
command_name: Command name
|
284
|
-
handler: Handler function
|
285
|
-
metadata: Command metadata
|
286
|
-
|
287
|
-
Returns:
|
288
|
-
Tuple[bool, List[str]]: Validity flag and list of errors
|
289
|
-
"""
|
290
|
-
errors = []
|
291
|
-
|
292
|
-
# Apply all validators
|
293
|
-
for validator in self._validators:
|
294
|
-
try:
|
295
|
-
# Check validity using the validator
|
296
|
-
is_valid, validator_errors = validator.validate(handler, command_name, metadata)
|
297
|
-
|
298
|
-
# Add errors to the general list
|
299
|
-
errors.extend(validator_errors)
|
300
|
-
except Exception as e:
|
301
|
-
logger.warning(f"Error validating command '{command_name}' with validator {validator.__class__.__name__}: {str(e)}")
|
302
|
-
errors.append(f"Validation error: {str(e)}")
|
303
|
-
|
304
|
-
# In strict mode, propagate the exception
|
305
|
-
if self.strict:
|
306
|
-
raise
|
307
|
-
|
308
|
-
return len(errors) == 0, errors
|
309
|
-
|
310
|
-
def register_command(self, command_name: str, handler: Callable) -> bool:
|
311
|
-
"""
|
312
|
-
Registers a command based on a handler function.
|
313
|
-
|
314
|
-
Args:
|
315
|
-
command_name: Command name
|
316
|
-
handler: Handler function
|
317
|
-
|
318
|
-
Returns:
|
319
|
-
bool: True if the command was registered successfully, False otherwise
|
320
|
-
"""
|
321
|
-
try:
|
322
|
-
# Analyze the handler
|
323
|
-
metadata = self.analyze_handler(command_name, handler)
|
324
|
-
|
325
|
-
# Validate metadata
|
326
|
-
is_valid, errors = self.validate_handler(command_name, handler, metadata)
|
327
|
-
|
328
|
-
# Output errors
|
329
|
-
if not is_valid:
|
330
|
-
logger.warning(f"Errors in command '{command_name}':")
|
331
|
-
for error in errors:
|
332
|
-
logger.warning(f" - {error}")
|
333
|
-
|
334
|
-
# In strict mode without auto-fix, skip registration
|
335
|
-
if self.strict and not self.auto_fix:
|
336
|
-
logger.error(f"Command registration '{command_name}' skipped due to errors")
|
337
|
-
return False
|
338
|
-
|
339
|
-
# Register the command in the dispatcher
|
340
|
-
self.dispatcher.register_handler(
|
341
|
-
command=command_name,
|
342
|
-
handler=handler,
|
343
|
-
description=metadata.get("description", ""),
|
344
|
-
summary=metadata.get("summary", ""),
|
345
|
-
params=metadata.get("parameters", {})
|
346
|
-
)
|
347
|
-
|
348
|
-
# Save command information
|
349
|
-
self._commands_info[command_name] = {
|
350
|
-
"metadata": metadata,
|
351
|
-
"handler": handler
|
352
|
-
}
|
353
|
-
|
354
|
-
logger.info(f"Registered command '{command_name}'")
|
355
|
-
return True
|
356
|
-
except Exception as e:
|
357
|
-
logger.error(f"Error registering command '{command_name}': {str(e)}")
|
358
|
-
|
359
|
-
# In strict mode, propagate the exception
|
360
|
-
if self.strict:
|
361
|
-
raise
|
362
|
-
|
363
|
-
return False
|
364
|
-
|
365
|
-
def register_all_commands(self) -> Dict[str, Any]:
|
366
|
-
"""
|
367
|
-
Registers all found commands.
|
368
|
-
|
369
|
-
Returns:
|
370
|
-
Dict[str, Any]: Registration statistics
|
371
|
-
"""
|
372
|
-
# Counters
|
373
|
-
stats = {
|
374
|
-
"successful": 0,
|
375
|
-
"failed": 0,
|
376
|
-
"skipped": 0,
|
377
|
-
"total": 0
|
378
|
-
}
|
379
|
-
|
380
|
-
# Search for command handlers
|
381
|
-
handlers = self.find_command_handlers()
|
382
|
-
|
383
|
-
# Register each command
|
384
|
-
for command_name, handler in handlers.items():
|
385
|
-
# Skip help, it's already registered in the dispatcher
|
386
|
-
if command_name == "help":
|
387
|
-
stats["skipped"] += 1
|
388
|
-
continue
|
389
|
-
|
390
|
-
if self.register_command(command_name, handler):
|
391
|
-
stats["successful"] += 1
|
392
|
-
else:
|
393
|
-
stats["failed"] += 1
|
394
|
-
|
395
|
-
stats["total"] += 1
|
396
|
-
|
397
|
-
# Generate API interfaces
|
398
|
-
for generator in self._generators:
|
399
|
-
if hasattr(generator, "generate_endpoints"):
|
400
|
-
generator.generate_endpoints()
|
401
|
-
|
402
|
-
if hasattr(generator, "generate_schema"):
|
403
|
-
generator.generate_schema()
|
404
|
-
|
405
|
-
# Output statistics
|
406
|
-
logger.info(f"Command registration results:")
|
407
|
-
logger.info(f" - Successful: {stats['successful']}")
|
408
|
-
logger.info(f" - With errors: {stats['failed']}")
|
409
|
-
logger.info(f" - Skipped: {stats['skipped']}")
|
410
|
-
logger.info(f" - Total in dispatcher: {len(self.dispatcher.get_valid_commands())}")
|
411
|
-
|
412
|
-
if stats["failed"] > 0 and self.strict:
|
413
|
-
logger.warning("WARNING: Some commands were not registered due to errors")
|
414
|
-
logger.warning("Use strict=False to register all commands")
|
415
|
-
logger.warning("Or auto_fix=True to automatically fix errors")
|
416
|
-
|
417
|
-
return stats
|
418
|
-
|
419
|
-
def execute(self, command: str, **kwargs) -> Any:
|
420
|
-
"""
|
421
|
-
Executes a command through the dispatcher.
|
422
|
-
|
423
|
-
Args:
|
424
|
-
command: Command name
|
425
|
-
**kwargs: Command parameters
|
426
|
-
|
427
|
-
Returns:
|
428
|
-
Any: Command execution result
|
429
|
-
"""
|
430
|
-
return self.dispatcher.execute(command, **kwargs)
|
431
|
-
|
432
|
-
def get_commands_info(self) -> Dict[str, Dict[str, Any]]:
|
433
|
-
"""
|
434
|
-
Returns information about all registered commands.
|
435
|
-
|
436
|
-
Returns:
|
437
|
-
Dict[str, Dict[str, Any]]: Dictionary {command_name: information}
|
438
|
-
"""
|
439
|
-
return self.dispatcher.get_commands_info()
|