mcp-proxy-adapter 2.1.17__py3-none-any.whl → 3.0.1__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.
Files changed (135) hide show
  1. examples/__init__.py +19 -0
  2. examples/anti_patterns/README.md +51 -0
  3. examples/anti_patterns/__init__.py +9 -0
  4. examples/anti_patterns/bad_design/README.md +72 -0
  5. examples/anti_patterns/bad_design/global_state.py +170 -0
  6. examples/anti_patterns/bad_design/monolithic_command.py +272 -0
  7. examples/basic_example/README.md +245 -0
  8. examples/basic_example/__init__.py +8 -0
  9. examples/basic_example/commands/__init__.py +5 -0
  10. examples/basic_example/commands/echo_command.py +95 -0
  11. examples/basic_example/commands/math_command.py +151 -0
  12. examples/basic_example/commands/time_command.py +152 -0
  13. examples/basic_example/config.json +25 -0
  14. examples/basic_example/docs/EN/README.md +177 -0
  15. examples/basic_example/docs/RU/README.md +177 -0
  16. examples/basic_example/server.py +151 -0
  17. examples/basic_example/tests/conftest.py +243 -0
  18. examples/commands/echo_command.py +52 -0
  19. examples/commands/echo_result.py +65 -0
  20. examples/commands/get_date_command.py +98 -0
  21. examples/commands/new_uuid4_command.py +91 -0
  22. examples/complete_example/Dockerfile +24 -0
  23. examples/complete_example/README.md +92 -0
  24. examples/complete_example/__init__.py +8 -0
  25. examples/complete_example/commands/__init__.py +5 -0
  26. examples/complete_example/commands/system_command.py +328 -0
  27. examples/complete_example/config.json +41 -0
  28. examples/complete_example/configs/config.dev.yaml +40 -0
  29. examples/complete_example/configs/config.docker.yaml +40 -0
  30. examples/complete_example/docker-compose.yml +35 -0
  31. examples/complete_example/requirements.txt +20 -0
  32. examples/complete_example/server.py +139 -0
  33. examples/minimal_example/README.md +65 -0
  34. examples/minimal_example/__init__.py +8 -0
  35. examples/minimal_example/config.json +14 -0
  36. examples/minimal_example/main.py +136 -0
  37. examples/minimal_example/simple_server.py +163 -0
  38. examples/minimal_example/tests/conftest.py +171 -0
  39. examples/minimal_example/tests/test_hello_command.py +111 -0
  40. examples/minimal_example/tests/test_integration.py +181 -0
  41. examples/server.py +69 -0
  42. examples/simple_server.py +128 -0
  43. examples/test_server.py +134 -0
  44. examples/tool_description_example.py +82 -0
  45. mcp_proxy_adapter/__init__.py +33 -1
  46. mcp_proxy_adapter/api/__init__.py +0 -0
  47. mcp_proxy_adapter/api/app.py +391 -0
  48. mcp_proxy_adapter/api/handlers.py +229 -0
  49. mcp_proxy_adapter/api/middleware/__init__.py +49 -0
  50. mcp_proxy_adapter/api/middleware/auth.py +146 -0
  51. mcp_proxy_adapter/api/middleware/base.py +79 -0
  52. mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
  53. mcp_proxy_adapter/api/middleware/logging.py +96 -0
  54. mcp_proxy_adapter/api/middleware/performance.py +83 -0
  55. mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
  56. mcp_proxy_adapter/api/schemas.py +305 -0
  57. mcp_proxy_adapter/api/tool_integration.py +223 -0
  58. mcp_proxy_adapter/api/tools.py +198 -0
  59. mcp_proxy_adapter/commands/__init__.py +19 -0
  60. mcp_proxy_adapter/commands/base.py +301 -0
  61. mcp_proxy_adapter/commands/command_registry.py +231 -0
  62. mcp_proxy_adapter/commands/config_command.py +113 -0
  63. mcp_proxy_adapter/commands/health_command.py +136 -0
  64. mcp_proxy_adapter/commands/help_command.py +193 -0
  65. mcp_proxy_adapter/commands/result.py +215 -0
  66. mcp_proxy_adapter/config.py +195 -0
  67. mcp_proxy_adapter/core/__init__.py +0 -0
  68. mcp_proxy_adapter/core/errors.py +173 -0
  69. mcp_proxy_adapter/core/logging.py +205 -0
  70. mcp_proxy_adapter/core/utils.py +138 -0
  71. mcp_proxy_adapter/custom_openapi.py +125 -0
  72. mcp_proxy_adapter/openapi.py +403 -0
  73. mcp_proxy_adapter/py.typed +0 -0
  74. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  75. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  76. mcp_proxy_adapter/tests/__init__.py +0 -0
  77. mcp_proxy_adapter/tests/api/__init__.py +3 -0
  78. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
  79. mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
  80. mcp_proxy_adapter/tests/commands/__init__.py +3 -0
  81. mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
  82. mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
  83. mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
  84. mcp_proxy_adapter/tests/conftest.py +131 -0
  85. mcp_proxy_adapter/tests/functional/__init__.py +3 -0
  86. mcp_proxy_adapter/tests/functional/test_api.py +235 -0
  87. mcp_proxy_adapter/tests/integration/__init__.py +3 -0
  88. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
  89. mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
  90. mcp_proxy_adapter/tests/performance/__init__.py +3 -0
  91. mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
  92. mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
  93. mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
  94. mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
  95. mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
  96. mcp_proxy_adapter/tests/test_base_command.py +123 -0
  97. mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
  98. mcp_proxy_adapter/tests/test_command_registry.py +245 -0
  99. mcp_proxy_adapter/tests/test_config.py +127 -0
  100. mcp_proxy_adapter/tests/test_utils.py +65 -0
  101. mcp_proxy_adapter/tests/unit/__init__.py +3 -0
  102. mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
  103. mcp_proxy_adapter/tests/unit/test_config.py +217 -0
  104. mcp_proxy_adapter/version.py +3 -0
  105. mcp_proxy_adapter-3.0.1.dist-info/METADATA +200 -0
  106. mcp_proxy_adapter-3.0.1.dist-info/RECORD +109 -0
  107. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/top_level.txt +1 -0
  108. mcp_proxy_adapter/adapter.py +0 -697
  109. mcp_proxy_adapter/analyzers/__init__.py +0 -1
  110. mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
  111. mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
  112. mcp_proxy_adapter/dispatchers/__init__.py +0 -1
  113. mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
  114. mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -262
  115. mcp_proxy_adapter/examples/analyze_config.py +0 -141
  116. mcp_proxy_adapter/examples/basic_integration.py +0 -155
  117. mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
  118. mcp_proxy_adapter/examples/extension_example.py +0 -72
  119. mcp_proxy_adapter/examples/help_best_practices.py +0 -67
  120. mcp_proxy_adapter/examples/help_usage.py +0 -64
  121. mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
  122. mcp_proxy_adapter/examples/openapi_server.py +0 -383
  123. mcp_proxy_adapter/examples/project_structure_example.py +0 -47
  124. mcp_proxy_adapter/examples/testing_example.py +0 -64
  125. mcp_proxy_adapter/models.py +0 -47
  126. mcp_proxy_adapter/registry.py +0 -439
  127. mcp_proxy_adapter/schema.py +0 -257
  128. mcp_proxy_adapter/testing_utils.py +0 -112
  129. mcp_proxy_adapter/validators/__init__.py +0 -1
  130. mcp_proxy_adapter/validators/docstring_validator.py +0 -75
  131. mcp_proxy_adapter/validators/metadata_validator.py +0 -76
  132. mcp_proxy_adapter-2.1.17.dist-info/METADATA +0 -376
  133. mcp_proxy_adapter-2.1.17.dist-info/RECORD +0 -30
  134. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/WHEEL +0 -0
  135. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,257 +0,0 @@
1
- """
2
- Module for optimizing OpenAPI schema for MCP Proxy.
3
- """
4
- from typing import Dict, Any, List, Optional
5
-
6
- class SchemaOptimizer:
7
- """
8
- OpenAPI schema optimizer for use with MCP Proxy.
9
-
10
- This class transforms a standard OpenAPI schema into a format
11
- more suitable for use with MCP Proxy and AI models.
12
- """
13
-
14
- def optimize(
15
- self,
16
- schema: Dict[str, Any],
17
- cmd_endpoint: str,
18
- commands_info: Dict[str, Dict[str, Any]]
19
- ) -> Dict[str, Any]:
20
- """
21
- Optimizes OpenAPI schema for MCP Proxy.
22
-
23
- Args:
24
- schema: Original OpenAPI schema
25
- cmd_endpoint: Path for universal JSON-RPC endpoint
26
- commands_info: Information about registered commands
27
-
28
- Returns:
29
- Dict[str, Any]: Optimized schema
30
- """
31
- # Create a new schema in a format compatible with the proxy
32
- optimized = {
33
- "openapi": "3.0.2",
34
- "info": {
35
- "title": "Command Registry API",
36
- "description": "API for executing commands through MCPProxy",
37
- "version": "1.0.0"
38
- },
39
- "paths": {
40
- cmd_endpoint: {
41
- "post": {
42
- "summary": "Execute command",
43
- "description": "Universal endpoint for executing various commands",
44
- "operationId": "execute_command",
45
- "requestBody": {
46
- "content": {
47
- "application/json": {
48
- "schema": {
49
- "$ref": "#/components/schemas/CommandRequest"
50
- },
51
- "examples": {}
52
- }
53
- },
54
- "required": True
55
- },
56
- "responses": {
57
- "200": {
58
- "description": "Command executed successfully",
59
- "content": {
60
- "application/json": {
61
- "schema": {
62
- "$ref": "#/components/schemas/CommandResponse"
63
- }
64
- }
65
- }
66
- },
67
- "400": {
68
- "description": "Error in request or during command execution",
69
- "content": {
70
- "application/json": {
71
- "schema": {
72
- "type": "object",
73
- "properties": {
74
- "detail": {
75
- "type": "string",
76
- "description": "Error description"
77
- }
78
- }
79
- }
80
- }
81
- }
82
- }
83
- }
84
- }
85
- }
86
- },
87
- "components": {
88
- "schemas": {
89
- "CommandRequest": {
90
- "title": "CommandRequest",
91
- "description": "Command execution request",
92
- "type": "object",
93
- "required": ["command"],
94
- "properties": {
95
- "command": {
96
- "title": "Command",
97
- "description": "Command to execute",
98
- "type": "string",
99
- "enum": list(commands_info.keys())
100
- },
101
- "params": {
102
- "title": "Parameters",
103
- "description": "Command parameters, depend on command type",
104
- "oneOf": []
105
- }
106
- }
107
- },
108
- "CommandResponse": {
109
- "title": "CommandResponse",
110
- "description": "Command execution response",
111
- "type": "object",
112
- "required": ["result"],
113
- "properties": {
114
- "result": {
115
- "title": "Result",
116
- "description": "Command execution result"
117
- }
118
- }
119
- }
120
- },
121
- "examples": {}
122
- }
123
- }
124
-
125
- # Add parameter schemas and examples for each command
126
- for cmd_name, cmd_info in commands_info.items():
127
- param_schema_name = f"{cmd_name.capitalize()}Params"
128
- params = cmd_info.get("params", {})
129
- has_params = bool(params)
130
-
131
- # Define parameter schema (даже если params пустой, схема будет пустым объектом)
132
- param_schema = {
133
- "title": param_schema_name,
134
- "description": f"Parameters for command {cmd_name}",
135
- "type": "object",
136
- "properties": {},
137
- }
138
- required_params = []
139
- example_params = {}
140
-
141
- for param_name, param_info in params.items():
142
- param_property = {
143
- "title": param_name.capitalize(),
144
- "type": param_info.get("type", "string"),
145
- "description": param_info.get("description", "")
146
- }
147
- if "default" in param_info:
148
- param_property["default"] = param_info["default"]
149
- if "enum" in param_info:
150
- param_property["enum"] = param_info["enum"]
151
- param_schema["properties"][param_name] = param_property
152
- if param_info.get("required", False):
153
- required_params.append(param_name)
154
- if "example" in param_info:
155
- example_params[param_name] = param_info["example"]
156
- elif "default" in param_info:
157
- example_params[param_name] = param_info["default"]
158
- elif param_info.get("type") == "string":
159
- example_params[param_name] = "example_value"
160
- elif param_info.get("type") == "integer":
161
- example_params[param_name] = 1
162
- elif param_info.get("type") == "boolean":
163
- example_params[param_name] = False
164
-
165
- if required_params:
166
- param_schema["required"] = required_params
167
-
168
- # Добавляем схему параметров всегда, даже если она пустая
169
- optimized["components"]["schemas"][param_schema_name] = param_schema
170
-
171
- # Добавляем $ref на схему параметров в oneOf всегда
172
- optimized["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"].append({
173
- "$ref": f"#/components/schemas/{param_schema_name}"
174
- })
175
-
176
- # Пример использования команды
177
- example_id = f"{cmd_name}_example"
178
- example = {
179
- "summary": f"Example of using command {cmd_name}",
180
- "value": {
181
- "command": cmd_name
182
- }
183
- }
184
- if has_params:
185
- example["value"]["params"] = example_params
186
- optimized["components"]["examples"][example_id] = example
187
- optimized["paths"][cmd_endpoint]["post"]["requestBody"]["content"]["application/json"]["examples"][example_id] = {
188
- "$ref": f"#/components/examples/{example_id}"
189
- }
190
-
191
- # Для команд без параметров добавляем type: null в oneOf
192
- optimized_oneof = optimized["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"]
193
- optimized_oneof.append({"type": "null"})
194
-
195
- # Add tool descriptions to schema for AI models
196
- self._add_tool_descriptions(optimized, commands_info)
197
- return optimized
198
-
199
- def _add_tool_descriptions(
200
- self,
201
- schema: Dict[str, Any],
202
- commands_info: Dict[str, Dict[str, Any]]
203
- ) -> None:
204
- """
205
- Adds AI tool descriptions to the schema.
206
-
207
- This method enhances the OpenAPI schema with special descriptions
208
- for better integration with AI models and MCPProxy.
209
-
210
- Args:
211
- schema: OpenAPI schema to enhance
212
- commands_info: Information about registered commands
213
- """
214
- # Add AI tool descriptions to x-mcp-tools
215
- schema["x-mcp-tools"] = []
216
-
217
- for cmd_name, cmd_info in commands_info.items():
218
- # Create tool description
219
- tool = {
220
- "name": f"mcp_{cmd_name}", # Add mcp_ prefix to command name
221
- "description": cmd_info.get("description", "") or cmd_info.get("summary", ""),
222
- "parameters": {
223
- "type": "object",
224
- "properties": {},
225
- "required": []
226
- }
227
- }
228
-
229
- # Add parameters
230
- for param_name, param_info in cmd_info.get("params", {}).items():
231
- # Convert parameter to JSON Schema format
232
- param_schema = {}
233
-
234
- # Parameter type
235
- param_schema["type"] = param_info.get("type", "string")
236
-
237
- # Description
238
- if "description" in param_info:
239
- param_schema["description"] = param_info["description"]
240
-
241
- # Default value
242
- if "default" in param_info:
243
- param_schema["default"] = param_info["default"]
244
-
245
- # Possible values
246
- if "enum" in param_info:
247
- param_schema["enum"] = param_info["enum"]
248
-
249
- # Add parameter to schema
250
- tool["parameters"]["properties"][param_name] = param_schema
251
-
252
- # If parameter is required, add to required list
253
- if param_info.get("required", False):
254
- tool["parameters"]["required"].append(param_name)
255
-
256
- # Add tool to list
257
- schema["x-mcp-tools"].append(tool)
@@ -1,112 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Test utilities for MCP Proxy Adapter: mock dispatcher, registry, and OpenAPI generator.
4
- Can be used in examples and tests.
5
- """
6
-
7
- def success_command(value: int = 1) -> dict:
8
- return {"result": value * 2}
9
-
10
- def error_command() -> None:
11
- raise ValueError("Test error")
12
-
13
- def param_command(required_param: str, optional_param: int = 0) -> dict:
14
- return {"required": required_param, "optional": optional_param}
15
-
16
- def complex_param_command(array_param: list, object_param: dict, bool_param: bool = True) -> dict:
17
- return {
18
- "array_length": len(array_param),
19
- "object_keys": list(object_param.keys()),
20
- "bool_value": bool_param
21
- }
22
-
23
- def type_error_command(param: int) -> dict:
24
- return {"param": param + 1}
25
-
26
- class MockDispatcher:
27
- def __init__(self):
28
- self.commands = {
29
- "success": success_command,
30
- "error": error_command,
31
- "param": param_command,
32
- "execute": self.execute_from_params
33
- }
34
- self.commands_info = {
35
- "success": {
36
- "description": "Successful command",
37
- "params": {"value": {"type": "integer", "description": "Input value", "required": False, "default": 1}}
38
- },
39
- "error": {"description": "Command with error", "params": {}},
40
- "param": {
41
- "description": "Command with parameters",
42
- "params": {
43
- "required_param": {"type": "string", "description": "Required parameter", "required": True},
44
- "optional_param": {"type": "integer", "description": "Optional parameter", "required": False, "default": 0}
45
- }
46
- },
47
- "execute": {
48
- "description": "Universal command for executing other commands",
49
- "params": {"query": {"type": "string", "description": "Command or query to execute", "required": False}}
50
- },
51
- "complex_param": {
52
- "description": "Command with complex parameters",
53
- "params": {
54
- "array_param": {"type": "array", "description": "Array of values", "required": True},
55
- "object_param": {"type": "object", "description": "Object", "required": True},
56
- "bool_param": {"type": "boolean", "description": "Boolean value", "required": False, "default": True}
57
- }
58
- },
59
- "type_error": {
60
- "description": "Command that will raise TypeError",
61
- "params": {"param": {"type": "integer", "description": "Integer parameter", "required": True}}
62
- }
63
- }
64
-
65
- def execute_from_params(self, **params):
66
- if "query" in params and params["query"] in self.commands:
67
- command = params.pop("query")
68
- return self.execute(command, **params)
69
- return {"available_commands": self.get_valid_commands(), "received_params": params}
70
-
71
- def execute(self, command, **params):
72
- if command not in self.commands:
73
- raise KeyError(f"Unknown command: {command}")
74
- return self.commands[command](**params)
75
-
76
- def get_valid_commands(self):
77
- return list(self.commands.keys())
78
-
79
- def get_command_info(self, command):
80
- return self.commands_info.get(command)
81
-
82
- def get_commands_info(self):
83
- return self.commands_info
84
-
85
- class MockRegistry:
86
- def __init__(self, use_openapi_generator=False):
87
- self.dispatcher = MockDispatcher()
88
- self.generators = []
89
- self.use_openapi_generator = use_openapi_generator
90
-
91
- def get_commands_info(self):
92
- return self.dispatcher.get_commands_info()
93
-
94
- def add_generator(self, generator):
95
- self.generators.append(generator)
96
- if hasattr(generator, 'set_dispatcher'):
97
- generator.set_dispatcher(self.dispatcher)
98
-
99
- class MockOpenApiGenerator:
100
- def __init__(self, **kwargs):
101
- self.dispatcher = None
102
- self.kwargs = kwargs
103
-
104
- def set_dispatcher(self, dispatcher):
105
- self.dispatcher = dispatcher
106
-
107
- def generate_schema(self):
108
- return {
109
- "openapi": "3.0.0",
110
- "info": {"title": "Mock API", "version": "1.0.0"},
111
- "paths": {}
112
- }
@@ -1 +0,0 @@
1
-
@@ -1,75 +0,0 @@
1
- """
2
- Validator for checking the correspondence between docstrings and function signatures.
3
- """
4
- import inspect
5
- from typing import Dict, Any, Optional, Callable, List, Tuple, get_type_hints
6
- import docstring_parser
7
-
8
- class DocstringValidator:
9
- """
10
- Validator for checking the correspondence between docstrings and handler functions.
11
-
12
- This class verifies that function docstrings match their signatures,
13
- contain all necessary sections, and describe all parameters.
14
- """
15
-
16
- def validate(self, handler: Callable, command_name: str, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
17
- """
18
- Validates the function's docstring against its formal parameters.
19
-
20
- Args:
21
- handler: Command handler function
22
- command_name: Command name
23
- metadata: Command metadata
24
-
25
- Returns:
26
- Tuple[bool, List[str]]: Validity flag and list of errors
27
- """
28
- errors = []
29
-
30
- # Get formal parameters of the function
31
- sig = inspect.signature(handler)
32
- formal_params = list(sig.parameters.keys())
33
-
34
- # Skip self parameter for methods
35
- if formal_params and formal_params[0] == 'self':
36
- formal_params = formal_params[1:]
37
-
38
- # Parse docstring
39
- docstring = handler.__doc__ or ""
40
- parsed_doc = docstring_parser.parse(docstring)
41
-
42
- # Check for function description
43
- if not parsed_doc.short_description and not parsed_doc.long_description:
44
- errors.append(f"Missing function description")
45
-
46
- # Get parameters from docstring
47
- doc_params = {param.arg_name: param for param in parsed_doc.params}
48
-
49
- # Check that all formal parameters are described in the docstring
50
- for param in formal_params:
51
- # Skip special parameters
52
- if param in ('params', 'kwargs'):
53
- continue
54
-
55
- if param not in doc_params:
56
- errors.append(f"Parameter '{param}' is not described in the function docstring")
57
-
58
- # Check for returns in docstring
59
- if not parsed_doc.returns and not any(t.type_name == 'Returns' for t in parsed_doc.meta):
60
- errors.append(f"Missing return value description in the function docstring")
61
-
62
- # Check for type annotations
63
- try:
64
- type_hints = get_type_hints(handler)
65
- for param in formal_params:
66
- # Skip special parameters
67
- if param in ('params', 'kwargs'):
68
- continue
69
-
70
- if param not in type_hints:
71
- errors.append(f"Missing type annotation for parameter '{param}' in function {command_name}")
72
- except Exception as e:
73
- errors.append(f"Error getting type hints: {str(e)}")
74
-
75
- return len(errors) == 0, errors
@@ -1,76 +0,0 @@
1
- """
2
- Validator for checking command metadata against function signatures.
3
- """
4
- import inspect
5
- from typing import Dict, Any, Optional, Callable, List, Tuple
6
-
7
- class MetadataValidator:
8
- """
9
- Validator for checking handler function metadata.
10
-
11
- This class verifies that command metadata matches function signatures,
12
- and all parameters are correctly described.
13
- """
14
-
15
- def validate(self, handler: Callable, command_name: str, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
16
- """
17
- Checks if metadata matches function's formal parameters.
18
-
19
- Args:
20
- handler: Command handler function
21
- command_name: Command name
22
- metadata: Command metadata
23
-
24
- Returns:
25
- Tuple[bool, List[str]]: Validity flag and list of errors
26
- """
27
- errors = []
28
-
29
- # Check presence of main fields in metadata
30
- if not metadata.get('description'):
31
- errors.append(f"Missing description for command '{command_name}'")
32
-
33
- # Get function's formal parameters
34
- sig = inspect.signature(handler)
35
- formal_params = list(sig.parameters.keys())
36
-
37
- # Skip self parameter for methods
38
- if formal_params and formal_params[0] == 'self':
39
- formal_params = formal_params[1:]
40
-
41
- # Check presence of parameters in metadata
42
- if 'parameters' not in metadata or not isinstance(metadata['parameters'], dict):
43
- errors.append(f"Parameters are missing or incorrectly defined in metadata for command '{command_name}'")
44
- return False, errors
45
-
46
- meta_params = metadata['parameters']
47
-
48
- # Check that all formal parameters are in metadata
49
- for param in formal_params:
50
- # Skip special parameters
51
- if param in ('params', 'kwargs'):
52
- continue
53
-
54
- if param not in meta_params:
55
- errors.append(f"Parameter '{param}' is not described in metadata for command '{command_name}'")
56
- continue
57
-
58
- param_info = meta_params[param]
59
-
60
- # Check presence of required fields for parameter
61
- if not isinstance(param_info, dict):
62
- errors.append(f"Incorrect format for parameter '{param}' description in metadata for command '{command_name}'")
63
- continue
64
-
65
- if 'type' not in param_info:
66
- errors.append(f"Type is not specified for parameter '{param}' in metadata for command '{command_name}'")
67
-
68
- if 'description' not in param_info:
69
- errors.append(f"Description is not specified for parameter '{param}' in metadata for command '{command_name}'")
70
-
71
- # Check that there are no extra parameters in metadata
72
- for param in meta_params:
73
- if param not in formal_params and param not in ('params', 'kwargs'):
74
- errors.append(f"Extra parameter '{param}' in metadata for command '{command_name}'")
75
-
76
- return len(errors) == 0, errors