mcp-proxy-adapter 6.2.23__py3-none-any.whl → 6.2.25__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/api/app.py +0 -3
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +10 -10
- mcp_proxy_adapter/commands/health_command.py +1 -1
- mcp_proxy_adapter/config.py +16 -4
- mcp_proxy_adapter/core/protocol_manager.py +9 -9
- mcp_proxy_adapter/examples/create_certificates_simple.py +7 -17
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +70 -33
- mcp_proxy_adapter/examples/run_full_test_suite.py +302 -109
- mcp_proxy_adapter/examples/run_security_tests.py +14 -5
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/main.py +0 -2
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/RECORD +33 -17
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.2.23.dist-info → mcp_proxy_adapter-6.2.25.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/api/app.py
CHANGED
@@ -350,9 +350,6 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
350
350
|
)
|
351
351
|
|
352
352
|
# Setup middleware using the new middleware package
|
353
|
-
print(f"DEBUG create_app: calling setup_middleware with config type: {type(current_config)}")
|
354
|
-
if hasattr(current_config, 'keys'):
|
355
|
-
print(f"DEBUG create_app: current_config keys: {list(current_config.keys())}")
|
356
353
|
setup_middleware(app, current_config)
|
357
354
|
|
358
355
|
# Use custom OpenAPI schema
|
@@ -87,7 +87,7 @@ class ProtocolMiddleware(BaseHTTPMiddleware):
|
|
87
87
|
Returns:
|
88
88
|
Response object
|
89
89
|
"""
|
90
|
-
logger.
|
90
|
+
logger.debug(f"ProtocolMiddleware.dispatch called for {request.method} {request.url.path}")
|
91
91
|
try:
|
92
92
|
# Get protocol from request
|
93
93
|
protocol = self._get_request_protocol(request)
|
@@ -169,32 +169,32 @@ def setup_protocol_middleware(app, app_config: Optional[Dict[str, Any]] = None):
|
|
169
169
|
app: FastAPI application
|
170
170
|
app_config: Application configuration dictionary (optional)
|
171
171
|
"""
|
172
|
-
logger.
|
172
|
+
logger.debug(f"setup_protocol_middleware - app_config type: {type(app_config)}")
|
173
173
|
|
174
174
|
# Check if protocol management is enabled
|
175
175
|
if app_config is None:
|
176
176
|
from mcp_proxy_adapter.config import config
|
177
177
|
app_config = config.get_all()
|
178
|
-
logger.
|
178
|
+
logger.debug(f"setup_protocol_middleware - loaded from global config, type: {type(app_config)}")
|
179
179
|
|
180
|
-
logger.
|
180
|
+
logger.debug(f"setup_protocol_middleware - final app_config type: {type(app_config)}")
|
181
181
|
|
182
182
|
if hasattr(app_config, 'get'):
|
183
|
-
logger.
|
183
|
+
logger.debug(f"setup_protocol_middleware - app_config keys: {list(app_config.keys()) if hasattr(app_config, 'keys') else 'no keys'}")
|
184
184
|
protocols_config = app_config.get("protocols", {})
|
185
|
-
logger.
|
185
|
+
logger.debug(f"setup_protocol_middleware - protocols_config type: {type(protocols_config)}")
|
186
186
|
enabled = protocols_config.get("enabled", True) if hasattr(protocols_config, 'get') else True
|
187
187
|
else:
|
188
|
-
logger.
|
188
|
+
logger.debug(f"setup_protocol_middleware - app_config is not dict-like: {repr(app_config)}")
|
189
189
|
enabled = True
|
190
190
|
|
191
|
-
logger.
|
191
|
+
logger.debug(f"setup_protocol_middleware - protocol management enabled: {enabled}")
|
192
192
|
|
193
193
|
if enabled:
|
194
194
|
# Create protocol middleware with current configuration
|
195
|
-
logger.
|
195
|
+
logger.debug(f"setup_protocol_middleware - creating ProtocolMiddleware with config type: {type(app_config)}")
|
196
196
|
middleware = ProtocolMiddleware(app, app_config)
|
197
|
-
logger.
|
197
|
+
logger.debug(f"setup_protocol_middleware - adding middleware to app")
|
198
198
|
app.add_middleware(ProtocolMiddleware, app_config=app_config)
|
199
199
|
logger.info("Protocol middleware added to application")
|
200
200
|
else:
|
mcp_proxy_adapter/config.py
CHANGED
@@ -118,18 +118,24 @@ class Config:
|
|
118
118
|
"auto_register_on_startup": True,
|
119
119
|
"auto_unregister_on_shutdown": True
|
120
120
|
},
|
121
|
+
"commands": {
|
122
|
+
"auto_discovery": True,
|
123
|
+
"enabled_commands": ["health", "echo", "list", "help"],
|
124
|
+
"disabled_commands": [],
|
125
|
+
"custom_commands_path": "./commands"
|
126
|
+
},
|
121
127
|
"debug": {
|
122
128
|
"enabled": False,
|
123
129
|
"level": "WARNING"
|
124
130
|
},
|
125
131
|
"security": {
|
126
132
|
"framework": "mcp_security_framework",
|
127
|
-
"enabled":
|
133
|
+
"enabled": False,
|
128
134
|
"debug": False,
|
129
135
|
"environment": "dev",
|
130
136
|
"version": "1.0.0",
|
131
137
|
"auth": {
|
132
|
-
"enabled":
|
138
|
+
"enabled": False,
|
133
139
|
"methods": ["api_key"],
|
134
140
|
"api_keys": {},
|
135
141
|
"user_roles": {},
|
@@ -175,7 +181,7 @@ class Config:
|
|
175
181
|
"renewal_threshold_days": 30
|
176
182
|
},
|
177
183
|
"permissions": {
|
178
|
-
"enabled":
|
184
|
+
"enabled": False,
|
179
185
|
"roles_file": None,
|
180
186
|
"default_role": "guest",
|
181
187
|
"admin_role": "admin",
|
@@ -187,7 +193,7 @@ class Config:
|
|
187
193
|
"roles": None
|
188
194
|
},
|
189
195
|
"rate_limit": {
|
190
|
-
"enabled":
|
196
|
+
"enabled": False,
|
191
197
|
"default_requests_per_minute": 60,
|
192
198
|
"default_requests_per_hour": 1000,
|
193
199
|
"burst_limit": 2,
|
@@ -212,6 +218,12 @@ class Config:
|
|
212
218
|
"include_level": True,
|
213
219
|
"include_module": True
|
214
220
|
}
|
221
|
+
},
|
222
|
+
"protocols": {
|
223
|
+
"enabled": True,
|
224
|
+
"allowed_protocols": ["http", "jsonrpc"],
|
225
|
+
"default_protocol": "http",
|
226
|
+
"auto_discovery": True
|
215
227
|
}
|
216
228
|
}
|
217
229
|
|
@@ -35,28 +35,28 @@ class ProtocolManager:
|
|
35
35
|
"""Load protocol configuration from config."""
|
36
36
|
# Use provided config or fallback to global config; normalize types
|
37
37
|
current_config = self.app_config if self.app_config is not None else config.get_all()
|
38
|
-
logger.
|
38
|
+
logger.debug(f"ProtocolManager._load_config - current_config type: {type(current_config)}")
|
39
39
|
|
40
40
|
if not hasattr(current_config, 'get'):
|
41
41
|
# Not a dict-like config, fallback to global
|
42
|
-
logger.
|
42
|
+
logger.debug(f"ProtocolManager._load_config - current_config is not dict-like, falling back to global config")
|
43
43
|
current_config = config.get_all()
|
44
44
|
|
45
|
-
logger.
|
45
|
+
logger.debug(f"ProtocolManager._load_config - final current_config type: {type(current_config)}")
|
46
46
|
if hasattr(current_config, 'get'):
|
47
|
-
logger.
|
47
|
+
logger.debug(f"ProtocolManager._load_config - current_config keys: {list(current_config.keys()) if hasattr(current_config, 'keys') else 'no keys'}")
|
48
48
|
|
49
49
|
# Get protocols configuration
|
50
|
-
logger.
|
50
|
+
logger.debug(f"ProtocolManager._load_config - before getting protocols")
|
51
51
|
try:
|
52
52
|
self.protocols_config = current_config.get("protocols", {})
|
53
|
-
logger.
|
53
|
+
logger.debug(f"ProtocolManager._load_config - protocols_config type: {type(self.protocols_config)}")
|
54
54
|
if hasattr(self.protocols_config, 'get'):
|
55
|
-
logger.
|
55
|
+
logger.debug(f"ProtocolManager._load_config - protocols_config is dict-like")
|
56
56
|
else:
|
57
|
-
logger.
|
57
|
+
logger.debug(f"ProtocolManager._load_config - protocols_config is NOT dict-like: {repr(self.protocols_config)}")
|
58
58
|
except Exception as e:
|
59
|
-
logger.
|
59
|
+
logger.debug(f"ProtocolManager._load_config - ERROR getting protocols: {e}")
|
60
60
|
self.protocols_config = {}
|
61
61
|
|
62
62
|
self.enabled = self.protocols_config.get("enabled", True) if hasattr(self.protocols_config, 'get') else True
|
@@ -466,18 +466,13 @@ class SimpleCertificateCreator:
|
|
466
466
|
shutil.copy2(ca_cert, expected_ca_cert)
|
467
467
|
print(f"✅ Created CA certificate copy: {expected_ca_cert}")
|
468
468
|
|
469
|
-
# Server certificate
|
469
|
+
# Server certificate copy
|
470
470
|
server_cert = self.certs_dir / "server_cert.pem"
|
471
471
|
expected_server_cert = self.certs_dir / "localhost_server.crt"
|
472
472
|
if server_cert.exists() and not expected_server_cert.exists():
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
except OSError:
|
477
|
-
# On Windows, symlink might require admin privileges, copy instead
|
478
|
-
import shutil
|
479
|
-
shutil.copy2(server_cert, expected_server_cert)
|
480
|
-
print(f"✅ Created server certificate copy: {expected_server_cert}")
|
473
|
+
import shutil
|
474
|
+
shutil.copy2(server_cert, expected_server_cert)
|
475
|
+
print(f"✅ Created server certificate copy: {expected_server_cert}")
|
481
476
|
|
482
477
|
# Server key symlink - check if it's in certs or keys directory
|
483
478
|
server_key_certs = self.certs_dir / "server_key.pem"
|
@@ -492,14 +487,9 @@ class SimpleCertificateCreator:
|
|
492
487
|
if server_key_keys.exists():
|
493
488
|
expected_server_key = self.keys_dir / "localhost_server.key"
|
494
489
|
if not expected_server_key.exists():
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
except OSError:
|
499
|
-
# On Windows, symlink might require admin privileges, copy instead
|
500
|
-
import shutil
|
501
|
-
shutil.copy2(server_key_keys, expected_server_key)
|
502
|
-
print(f"✅ Created server key copy: {expected_server_key}")
|
490
|
+
import shutil
|
491
|
+
shutil.copy2(server_key_keys, expected_server_key)
|
492
|
+
print(f"✅ Created server key copy: {expected_server_key}")
|
503
493
|
|
504
494
|
# Print summary
|
505
495
|
print("\n" + "=" * 60)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"""Basic Framework Example.
|
2
|
+
|
3
|
+
This example demonstrates the fundamental usage of MCP Proxy Adapter
|
4
|
+
with minimal configuration and basic command registration.
|
5
|
+
|
6
|
+
Note: This package provides a basic example of MCP Proxy Adapter usage.
|
7
|
+
The main application is created dynamically in main.py and not exported
|
8
|
+
as a global variable for this example.
|
9
|
+
"""
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Basic Framework Example
|
4
|
+
This example demonstrates the basic usage of the MCP Proxy Adapter framework
|
5
|
+
with minimal configuration and built-in commands.
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
import sys
|
10
|
+
import argparse
|
11
|
+
from pathlib import Path
|
12
|
+
# Add the framework to the path
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
14
|
+
from mcp_proxy_adapter.core.app_factory import create_and_run_server
|
15
|
+
def main():
|
16
|
+
"""Main entry point for the basic framework example."""
|
17
|
+
parser = argparse.ArgumentParser(description="Basic Framework Example")
|
18
|
+
parser.add_argument("--config", "-c", required=True, help="Path to configuration file")
|
19
|
+
parser.add_argument("--host", help="Server host")
|
20
|
+
parser.add_argument("--port", type=int, help="Server port")
|
21
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
22
|
+
args = parser.parse_args()
|
23
|
+
# Override configuration if specified
|
24
|
+
config_overrides = {}
|
25
|
+
if args.host:
|
26
|
+
config_overrides["host"] = args.host
|
27
|
+
if args.port:
|
28
|
+
config_overrides["port"] = args.port
|
29
|
+
if args.debug:
|
30
|
+
config_overrides["debug"] = True
|
31
|
+
print(f"🚀 Starting Basic Framework Example")
|
32
|
+
print(f"📋 Configuration: {args.config}")
|
33
|
+
print("=" * 50)
|
34
|
+
# Use the factory method to create and run the server
|
35
|
+
create_and_run_server(
|
36
|
+
config_path=args.config,
|
37
|
+
title="Basic Framework Example",
|
38
|
+
description="Basic MCP Proxy Adapter with minimal configuration",
|
39
|
+
version="1.0.0",
|
40
|
+
host=config_overrides.get("host", "0.0.0.0"),
|
41
|
+
log_level="debug" if config_overrides.get("debug", False) else "info"
|
42
|
+
)
|
43
|
+
if __name__ == "__main__":
|
44
|
+
main()
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""Full Application Example.
|
2
|
+
|
3
|
+
This example demonstrates advanced usage of MCP Proxy Adapter including:
|
4
|
+
- Proxy registration endpoints
|
5
|
+
- Custom command hooks
|
6
|
+
- Advanced security configurations
|
7
|
+
- Role-based access control
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .main import get_app
|
11
|
+
app = get_app()
|
12
|
+
from .proxy_endpoints import router as proxy_router
|
@@ -0,0 +1,80 @@
|
|
1
|
+
"""
|
2
|
+
Custom Echo Command
|
3
|
+
This module demonstrates a custom command implementation for the full application example.
|
4
|
+
Author: Vasiliy Zdanovskiy
|
5
|
+
email: vasilyvz@gmail.com
|
6
|
+
"""
|
7
|
+
from typing import Dict, Any, Optional
|
8
|
+
from mcp_proxy_adapter.commands.base import BaseCommand
|
9
|
+
from mcp_proxy_adapter.commands.result import CommandResult
|
10
|
+
class CustomEchoResult(CommandResult):
|
11
|
+
"""Result class for custom echo command."""
|
12
|
+
def __init__(self, message: str, timestamp: str, echo_count: int):
|
13
|
+
self.message = message
|
14
|
+
self.timestamp = timestamp
|
15
|
+
self.echo_count = echo_count
|
16
|
+
def to_dict(self) -> Dict[str, Any]:
|
17
|
+
"""Convert result to dictionary."""
|
18
|
+
return {
|
19
|
+
"message": self.message,
|
20
|
+
"timestamp": self.timestamp,
|
21
|
+
"echo_count": self.echo_count,
|
22
|
+
"command_type": "custom_echo"
|
23
|
+
}
|
24
|
+
def get_schema(self) -> Dict[str, Any]:
|
25
|
+
"""Get result schema."""
|
26
|
+
return {
|
27
|
+
"type": "object",
|
28
|
+
"properties": {
|
29
|
+
"message": {"type": "string", "description": "Echoed message"},
|
30
|
+
"timestamp": {"type": "string", "description": "Timestamp of echo"},
|
31
|
+
"echo_count": {"type": "integer", "description": "Number of echoes"},
|
32
|
+
"command_type": {"type": "string", "description": "Command type"}
|
33
|
+
},
|
34
|
+
"required": ["message", "timestamp", "echo_count", "command_type"]
|
35
|
+
}
|
36
|
+
class CustomEchoCommand(BaseCommand):
|
37
|
+
"""Custom echo command implementation."""
|
38
|
+
def __init__(self):
|
39
|
+
super().__init__()
|
40
|
+
self.echo_count = 0
|
41
|
+
def get_name(self) -> str:
|
42
|
+
"""Get command name."""
|
43
|
+
return "custom_echo"
|
44
|
+
def get_description(self) -> str:
|
45
|
+
"""Get command description."""
|
46
|
+
return "Custom echo command with enhanced features"
|
47
|
+
def get_schema(self) -> Dict[str, Any]:
|
48
|
+
"""Get command schema."""
|
49
|
+
return {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {
|
52
|
+
"message": {
|
53
|
+
"type": "string",
|
54
|
+
"description": "Message to echo",
|
55
|
+
"default": "Hello from custom echo!"
|
56
|
+
},
|
57
|
+
"repeat": {
|
58
|
+
"type": "integer",
|
59
|
+
"description": "Number of times to repeat",
|
60
|
+
"default": 1,
|
61
|
+
"minimum": 1,
|
62
|
+
"maximum": 10
|
63
|
+
}
|
64
|
+
},
|
65
|
+
"required": ["message"]
|
66
|
+
}
|
67
|
+
async def execute(self, params: Dict[str, Any]) -> CustomEchoResult:
|
68
|
+
"""Execute the custom echo command."""
|
69
|
+
message = params.get("message", "Hello from custom echo!")
|
70
|
+
repeat = min(max(params.get("repeat", 1), 1), 10)
|
71
|
+
self.echo_count += 1
|
72
|
+
from datetime import datetime
|
73
|
+
timestamp = datetime.now().isoformat()
|
74
|
+
# Repeat the message
|
75
|
+
echoed_message = " ".join([message] * repeat)
|
76
|
+
return CustomEchoResult(
|
77
|
+
message=echoed_message,
|
78
|
+
timestamp=timestamp,
|
79
|
+
echo_count=self.echo_count
|
80
|
+
)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"""
|
2
|
+
Dynamic Calculator Command
|
3
|
+
This module demonstrates a dynamically loaded command implementation for the full application example.
|
4
|
+
Author: Vasiliy Zdanovskiy
|
5
|
+
email: vasilyvz@gmail.com
|
6
|
+
"""
|
7
|
+
from typing import Dict, Any, Optional
|
8
|
+
from mcp_proxy_adapter.commands.base import BaseCommand
|
9
|
+
from mcp_proxy_adapter.commands.result import CommandResult
|
10
|
+
class CalculatorResult(CommandResult):
|
11
|
+
"""Result class for calculator command."""
|
12
|
+
def __init__(self, operation: str, result: float, expression: str):
|
13
|
+
self.operation = operation
|
14
|
+
self.result = result
|
15
|
+
self.expression = expression
|
16
|
+
def to_dict(self) -> Dict[str, Any]:
|
17
|
+
"""Convert result to dictionary."""
|
18
|
+
return {
|
19
|
+
"operation": self.operation,
|
20
|
+
"result": self.result,
|
21
|
+
"expression": self.expression,
|
22
|
+
"command_type": "dynamic_calculator"
|
23
|
+
}
|
24
|
+
def get_schema(self) -> Dict[str, Any]:
|
25
|
+
"""Get result schema."""
|
26
|
+
return {
|
27
|
+
"type": "object",
|
28
|
+
"properties": {
|
29
|
+
"operation": {"type": "string", "description": "Mathematical operation"},
|
30
|
+
"result": {"type": "number", "description": "Calculation result"},
|
31
|
+
"expression": {"type": "string", "description": "Full expression"},
|
32
|
+
"command_type": {"type": "string", "description": "Command type"}
|
33
|
+
},
|
34
|
+
"required": ["operation", "result", "expression", "command_type"]
|
35
|
+
}
|
36
|
+
class DynamicCalculatorCommand(BaseCommand):
|
37
|
+
"""Dynamic calculator command implementation."""
|
38
|
+
def get_name(self) -> str:
|
39
|
+
"""Get command name."""
|
40
|
+
return "dynamic_calculator"
|
41
|
+
def get_description(self) -> str:
|
42
|
+
"""Get command description."""
|
43
|
+
return "Dynamic calculator with basic mathematical operations"
|
44
|
+
def get_schema(self) -> Dict[str, Any]:
|
45
|
+
"""Get command schema."""
|
46
|
+
return {
|
47
|
+
"type": "object",
|
48
|
+
"properties": {
|
49
|
+
"operation": {
|
50
|
+
"type": "string",
|
51
|
+
"description": "Mathematical operation (add, subtract, multiply, divide)",
|
52
|
+
"enum": ["add", "subtract", "multiply", "divide"]
|
53
|
+
},
|
54
|
+
"a": {
|
55
|
+
"type": "number",
|
56
|
+
"description": "First number"
|
57
|
+
},
|
58
|
+
"b": {
|
59
|
+
"type": "number",
|
60
|
+
"description": "Second number"
|
61
|
+
}
|
62
|
+
},
|
63
|
+
"required": ["operation", "a", "b"]
|
64
|
+
}
|
65
|
+
async def execute(self, params: Dict[str, Any]) -> CalculatorResult:
|
66
|
+
"""Execute the calculator command."""
|
67
|
+
operation = params.get("operation")
|
68
|
+
a = params.get("a")
|
69
|
+
b = params.get("b")
|
70
|
+
if operation == "add":
|
71
|
+
result = a + b
|
72
|
+
expression = f"{a} + {b}"
|
73
|
+
elif operation == "subtract":
|
74
|
+
result = a - b
|
75
|
+
expression = f"{a} - {b}"
|
76
|
+
elif operation == "multiply":
|
77
|
+
result = a * b
|
78
|
+
expression = f"{a} * {b}"
|
79
|
+
elif operation == "divide":
|
80
|
+
if b == 0:
|
81
|
+
raise ValueError("Division by zero is not allowed")
|
82
|
+
result = a / b
|
83
|
+
expression = f"{a} / {b}"
|
84
|
+
else:
|
85
|
+
raise ValueError(f"Unknown operation: {operation}")
|
86
|
+
return CalculatorResult(
|
87
|
+
operation=operation,
|
88
|
+
result=result,
|
89
|
+
expression=expression
|
90
|
+
)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
"""
|
2
|
+
Application Hooks
|
3
|
+
This module demonstrates application-level hooks in the full application example.
|
4
|
+
Author: Vasiliy Zdanovskiy
|
5
|
+
email: vasilyvz@gmail.com
|
6
|
+
"""
|
7
|
+
import logging
|
8
|
+
from typing import Dict, Any, Optional
|
9
|
+
from datetime import datetime
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
class ApplicationHooks:
|
12
|
+
"""Application-level hooks."""
|
13
|
+
@staticmethod
|
14
|
+
def on_startup():
|
15
|
+
"""Hook executed on application startup."""
|
16
|
+
logger.info("🚀 Application startup hook executed")
|
17
|
+
# Initialize application-specific resources
|
18
|
+
logger.info("📊 Initializing application metrics")
|
19
|
+
logger.info("🔐 Loading security configurations")
|
20
|
+
logger.info("📝 Setting up logging")
|
21
|
+
@staticmethod
|
22
|
+
def on_shutdown():
|
23
|
+
"""Hook executed on application shutdown."""
|
24
|
+
logger.info("🛑 Application shutdown hook executed")
|
25
|
+
# Cleanup application resources
|
26
|
+
logger.info("🧹 Cleaning up resources")
|
27
|
+
logger.info("💾 Saving application state")
|
28
|
+
logger.info("📊 Finalizing metrics")
|
29
|
+
@staticmethod
|
30
|
+
def before_request(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
31
|
+
"""Hook executed before processing any request."""
|
32
|
+
logger.info(f"🔧 Application hook: before_request with data: {request_data}")
|
33
|
+
# Add request metadata
|
34
|
+
request_data["app_metadata"] = {
|
35
|
+
"request_id": f"req_{datetime.now().timestamp()}",
|
36
|
+
"timestamp": datetime.now().isoformat(),
|
37
|
+
"application": "full_application_example"
|
38
|
+
}
|
39
|
+
return request_data
|
40
|
+
@staticmethod
|
41
|
+
def after_request(result: Dict[str, Any]) -> Dict[str, Any]:
|
42
|
+
"""Hook executed after processing any request."""
|
43
|
+
logger.info(f"🔧 Application hook: after_request with result: {result}")
|
44
|
+
# Add response metadata
|
45
|
+
result["app_response_metadata"] = {
|
46
|
+
"processed_at": datetime.now().isoformat(),
|
47
|
+
"application": "full_application_example",
|
48
|
+
"version": "1.0.0"
|
49
|
+
}
|
50
|
+
return result
|
51
|
+
@staticmethod
|
52
|
+
def on_error(error: Exception, context: Dict[str, Any]):
|
53
|
+
"""Hook executed when an error occurs."""
|
54
|
+
logger.error(f"🔧 Application hook: on_error - {error} in context: {context}")
|
55
|
+
# Log error details
|
56
|
+
logger.error(f"Error type: {type(error).__name__}")
|
57
|
+
logger.error(f"Error message: {str(error)}")
|
58
|
+
logger.error(f"Context: {context}")
|
59
|
+
@staticmethod
|
60
|
+
def on_command_registered(command_name: str, command_info: Dict[str, Any]):
|
61
|
+
"""Hook executed when a command is registered."""
|
62
|
+
logger.info(f"🔧 Application hook: on_command_registered - {command_name}")
|
63
|
+
logger.info(f"Command info: {command_info}")
|
64
|
+
# Track registered commands
|
65
|
+
logger.info(f"📝 Command '{command_name}' registered successfully")
|
66
|
+
@staticmethod
|
67
|
+
def on_command_executed(command_name: str, execution_time: float, success: bool):
|
68
|
+
"""Hook executed when a command is executed."""
|
69
|
+
logger.info(f"🔧 Application hook: on_command_executed - {command_name}")
|
70
|
+
logger.info(f"Execution time: {execution_time}s, Success: {success}")
|
71
|
+
# Track command execution metrics
|
72
|
+
if success:
|
73
|
+
logger.info(f"✅ Command '{command_name}' executed successfully in {execution_time}s")
|
74
|
+
else:
|
75
|
+
logger.warning(f"⚠️ Command '{command_name}' failed after {execution_time}s")
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""
|
2
|
+
Built-in Command Hooks
|
3
|
+
This module demonstrates hooks for built-in commands in the full application example.
|
4
|
+
Author: Vasiliy Zdanovskiy
|
5
|
+
email: vasilyvz@gmail.com
|
6
|
+
"""
|
7
|
+
import logging
|
8
|
+
from typing import Dict, Any, Optional
|
9
|
+
from datetime import datetime
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
class BuiltinCommandHooks:
|
12
|
+
"""Hooks for built-in commands."""
|
13
|
+
@staticmethod
|
14
|
+
def before_echo_command(params: Dict[str, Any]) -> Dict[str, Any]:
|
15
|
+
"""Hook executed before echo command."""
|
16
|
+
logger.info(f"🔧 Built-in hook: before_echo_command with params: {params}")
|
17
|
+
# Add timestamp to message
|
18
|
+
if "message" in params:
|
19
|
+
timestamp = datetime.now().isoformat()
|
20
|
+
params["message"] = f"[{timestamp}] {params['message']}"
|
21
|
+
return params
|
22
|
+
@staticmethod
|
23
|
+
def after_echo_command(result: Dict[str, Any]) -> Dict[str, Any]:
|
24
|
+
"""Hook executed after echo command."""
|
25
|
+
logger.info(f"🔧 Built-in hook: after_echo_command with result: {result}")
|
26
|
+
# Add hook metadata
|
27
|
+
result["hook_metadata"] = {
|
28
|
+
"hook_type": "builtin_after_echo",
|
29
|
+
"timestamp": datetime.now().isoformat(),
|
30
|
+
"processed": True
|
31
|
+
}
|
32
|
+
return result
|
33
|
+
@staticmethod
|
34
|
+
def before_health_command(params: Dict[str, Any]) -> Dict[str, Any]:
|
35
|
+
"""Hook executed before health command."""
|
36
|
+
logger.info(f"🔧 Built-in hook: before_health_command with params: {params}")
|
37
|
+
# Add custom health check parameters
|
38
|
+
params["include_detailed_info"] = True
|
39
|
+
params["check_dependencies"] = True
|
40
|
+
return params
|
41
|
+
@staticmethod
|
42
|
+
def after_health_command(result: Dict[str, Any]) -> Dict[str, Any]:
|
43
|
+
"""Hook executed after health command."""
|
44
|
+
logger.info(f"🔧 Built-in hook: after_health_command with result: {result}")
|
45
|
+
# Add custom health metrics
|
46
|
+
if "status" in result and result["status"] == "healthy":
|
47
|
+
result["custom_metrics"] = {
|
48
|
+
"uptime": "24h",
|
49
|
+
"memory_usage": "45%",
|
50
|
+
"cpu_usage": "12%"
|
51
|
+
}
|
52
|
+
return result
|
53
|
+
@staticmethod
|
54
|
+
def before_config_command(params: Dict[str, Any]) -> Dict[str, Any]:
|
55
|
+
"""Hook executed before config command."""
|
56
|
+
logger.info(f"🔧 Built-in hook: before_config_command with params: {params}")
|
57
|
+
# Add configuration validation
|
58
|
+
params["validate_config"] = True
|
59
|
+
params["include_secrets"] = False
|
60
|
+
return params
|
61
|
+
@staticmethod
|
62
|
+
def after_config_command(result: Dict[str, Any]) -> Dict[str, Any]:
|
63
|
+
"""Hook executed after config command."""
|
64
|
+
logger.info(f"🔧 Built-in hook: after_config_command with result: {result}")
|
65
|
+
# Add configuration metadata
|
66
|
+
result["config_metadata"] = {
|
67
|
+
"last_modified": datetime.now().isoformat(),
|
68
|
+
"version": "1.0.0",
|
69
|
+
"environment": "development"
|
70
|
+
}
|
71
|
+
return result
|