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.
- 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 +245 -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 +25 -0
- examples/basic_example/docs/EN/README.md +177 -0
- examples/basic_example/docs/RU/README.md +177 -0
- examples/basic_example/server.py +151 -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 +328 -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/requirements.txt +20 -0
- examples/complete_example/server.py +139 -0
- examples/minimal_example/README.md +65 -0
- examples/minimal_example/__init__.py +8 -0
- examples/minimal_example/config.json +14 -0
- examples/minimal_example/main.py +136 -0
- examples/minimal_example/simple_server.py +163 -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 +181 -0
- examples/server.py +69 -0
- examples/simple_server.py +128 -0
- examples/test_server.py +134 -0
- examples/tool_description_example.py +82 -0
- mcp_proxy_adapter/__init__.py +33 -1
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +391 -0
- mcp_proxy_adapter/api/handlers.py +229 -0
- mcp_proxy_adapter/api/middleware/__init__.py +49 -0
- mcp_proxy_adapter/api/middleware/auth.py +146 -0
- mcp_proxy_adapter/api/middleware/base.py +79 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
- mcp_proxy_adapter/api/middleware/logging.py +96 -0
- mcp_proxy_adapter/api/middleware/performance.py +83 -0
- mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
- mcp_proxy_adapter/api/schemas.py +305 -0
- mcp_proxy_adapter/api/tool_integration.py +223 -0
- mcp_proxy_adapter/api/tools.py +198 -0
- mcp_proxy_adapter/commands/__init__.py +19 -0
- mcp_proxy_adapter/commands/base.py +301 -0
- mcp_proxy_adapter/commands/command_registry.py +231 -0
- mcp_proxy_adapter/commands/config_command.py +113 -0
- mcp_proxy_adapter/commands/health_command.py +136 -0
- mcp_proxy_adapter/commands/help_command.py +193 -0
- mcp_proxy_adapter/commands/result.py +215 -0
- mcp_proxy_adapter/config.py +195 -0
- mcp_proxy_adapter/core/__init__.py +0 -0
- mcp_proxy_adapter/core/errors.py +173 -0
- mcp_proxy_adapter/core/logging.py +205 -0
- mcp_proxy_adapter/core/utils.py +138 -0
- mcp_proxy_adapter/custom_openapi.py +125 -0
- mcp_proxy_adapter/openapi.py +403 -0
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +3 -0
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
- mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
- mcp_proxy_adapter/tests/commands/__init__.py +3 -0
- mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
- mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
- mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
- mcp_proxy_adapter/tests/conftest.py +131 -0
- mcp_proxy_adapter/tests/functional/__init__.py +3 -0
- mcp_proxy_adapter/tests/functional/test_api.py +235 -0
- mcp_proxy_adapter/tests/integration/__init__.py +3 -0
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
- mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
- mcp_proxy_adapter/tests/performance/__init__.py +3 -0
- mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
- mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
- mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
- mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
- mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
- mcp_proxy_adapter/tests/test_base_command.py +123 -0
- mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
- mcp_proxy_adapter/tests/test_command_registry.py +245 -0
- mcp_proxy_adapter/tests/test_config.py +127 -0
- mcp_proxy_adapter/tests/test_utils.py +65 -0
- mcp_proxy_adapter/tests/unit/__init__.py +3 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
- mcp_proxy_adapter/tests/unit/test_config.py +217 -0
- mcp_proxy_adapter/version.py +3 -0
- mcp_proxy_adapter-3.0.1.dist-info/METADATA +200 -0
- mcp_proxy_adapter-3.0.1.dist-info/RECORD +109 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.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.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/licenses/LICENSE +0 -0
examples/__init__.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
Examples package for MCP Microservice.
|
3
|
+
|
4
|
+
This package contains examples of using the MCP Microservice framework
|
5
|
+
for building microservices with different levels of complexity.
|
6
|
+
|
7
|
+
Examples:
|
8
|
+
- minimal_example: Minimal working example with a single command
|
9
|
+
- basic_example: Basic example with multiple commands and tests
|
10
|
+
- complete_example: Complete example with Docker support and advanced features
|
11
|
+
- anti_patterns: Examples of anti-patterns and bad practices (for educational purposes)
|
12
|
+
"""
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"minimal_example",
|
16
|
+
"basic_example",
|
17
|
+
"complete_example",
|
18
|
+
"anti_patterns"
|
19
|
+
]
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Anti-Patterns for MCP Microservice
|
2
|
+
|
3
|
+
This directory contains examples of anti-patterns and bad practices when using MCP Microservice.
|
4
|
+
These examples demonstrate what **NOT** to do and common mistakes to avoid.
|
5
|
+
|
6
|
+
## Categories
|
7
|
+
|
8
|
+
1. **Bad Design Patterns** - Examples of poor architectural and design choices
|
9
|
+
2. **Performance Issues** - Examples that can cause performance problems
|
10
|
+
3. **Security Problems** - Examples with potential security vulnerabilities
|
11
|
+
|
12
|
+
## Purpose
|
13
|
+
|
14
|
+
These examples serve as educational resources to help you:
|
15
|
+
|
16
|
+
1. Recognize common mistakes
|
17
|
+
2. Understand why they are problematic
|
18
|
+
3. Learn better alternatives
|
19
|
+
|
20
|
+
**WARNING:** These examples contain intentional errors, bugs, and security vulnerabilities.
|
21
|
+
Do not use them in production environments or as a basis for your own code.
|
22
|
+
|
23
|
+
## Structure
|
24
|
+
|
25
|
+
```
|
26
|
+
anti_patterns/
|
27
|
+
├── bad_design/ # Poor architectural and design choices
|
28
|
+
│ ├── monolithic_command.py # Example of a command that does too much
|
29
|
+
│ ├── global_state.py # Example of using global state
|
30
|
+
│ └── README.md # Explanation of bad design patterns
|
31
|
+
├── performance_issues/ # Performance problems
|
32
|
+
│ ├── blocking_operations.py # Example of blocking the event loop
|
33
|
+
│ ├── memory_leaks.py # Example of potential memory leaks
|
34
|
+
│ └── README.md # Explanation of performance issues
|
35
|
+
├── security_problems/ # Security vulnerabilities
|
36
|
+
│ ├── no_validation.py # Example of missing input validation
|
37
|
+
│ ├── command_injection.py # Example of command injection vulnerability
|
38
|
+
│ └── README.md # Explanation of security problems
|
39
|
+
└── README.md # This file
|
40
|
+
```
|
41
|
+
|
42
|
+
## How to Use
|
43
|
+
|
44
|
+
Each example directory contains a README.md file explaining:
|
45
|
+
|
46
|
+
1. What the anti-pattern is
|
47
|
+
2. Why it's problematic
|
48
|
+
3. Better alternatives
|
49
|
+
4. How to fix the issues
|
50
|
+
|
51
|
+
Use these examples as a reference for what to avoid, not as templates for your code.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"""
|
2
|
+
Anti-patterns examples for MCP Microservice.
|
3
|
+
|
4
|
+
This package contains examples of anti-patterns and bad practices
|
5
|
+
when using MCP Microservice framework. These examples are for educational
|
6
|
+
purposes only and should NOT be used in production.
|
7
|
+
"""
|
8
|
+
|
9
|
+
__all__ = []
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Bad Design Patterns
|
2
|
+
|
3
|
+
This directory contains examples of poor architectural and design choices when using MCP Microservice.
|
4
|
+
|
5
|
+
## Included Examples
|
6
|
+
|
7
|
+
1. **[monolithic_command.py](./monolithic_command.py)** - A command that tries to do too much
|
8
|
+
2. **[global_state.py](./global_state.py)** - Using global state for command data
|
9
|
+
|
10
|
+
## Monolithic Command Anti-Pattern
|
11
|
+
|
12
|
+
### Problem
|
13
|
+
|
14
|
+
The `MonolithicCommand` tries to handle multiple responsibilities:
|
15
|
+
- Data processing
|
16
|
+
- Validation
|
17
|
+
- Database operations
|
18
|
+
- File operations
|
19
|
+
- Notification
|
20
|
+
|
21
|
+
### Why It's Bad
|
22
|
+
|
23
|
+
1. **Violates Single Responsibility Principle**: Each command should do one thing well
|
24
|
+
2. **Difficult to Test**: Large commands with many responsibilities are hard to test
|
25
|
+
3. **Hard to Maintain**: Changes to one feature may affect others
|
26
|
+
4. **Poor Reusability**: Cannot reuse parts of the functionality
|
27
|
+
|
28
|
+
### Better Alternative
|
29
|
+
|
30
|
+
Split the monolithic command into multiple smaller commands:
|
31
|
+
- `ProcessDataCommand` - Handles data processing
|
32
|
+
- `SaveDataCommand` - Handles database operations
|
33
|
+
- `GenerateReportCommand` - Handles file operations
|
34
|
+
- `SendNotificationCommand` - Handles notifications
|
35
|
+
|
36
|
+
## Global State Anti-Pattern
|
37
|
+
|
38
|
+
### Problem
|
39
|
+
|
40
|
+
The `GlobalStateCommand` uses global variables to store and share data between command executions.
|
41
|
+
|
42
|
+
### Why It's Bad
|
43
|
+
|
44
|
+
1. **Thread Safety**: Global state is not thread-safe in asynchronous environments
|
45
|
+
2. **Testing Difficulty**: Commands with global state are hard to test in isolation
|
46
|
+
3. **Hidden Dependencies**: Dependencies are not explicit
|
47
|
+
4. **State Management**: Difficult to track and manage state changes
|
48
|
+
|
49
|
+
### Better Alternative
|
50
|
+
|
51
|
+
Use proper dependency injection and maintain state within the command instance:
|
52
|
+
|
53
|
+
```python
|
54
|
+
class ProperStateCommand(Command):
|
55
|
+
name = "proper_state"
|
56
|
+
result_class = StateResult
|
57
|
+
|
58
|
+
def __init__(self):
|
59
|
+
self._state = {} # State is maintained per instance
|
60
|
+
|
61
|
+
async def execute(self, key: str, value: Any = None) -> StateResult:
|
62
|
+
if value is not None:
|
63
|
+
self._state[key] = value
|
64
|
+
return StateResult(key, self._state.get(key))
|
65
|
+
```
|
66
|
+
|
67
|
+
## How to Fix
|
68
|
+
|
69
|
+
1. **Identify Single Responsibilities**: Break down commands by responsibility
|
70
|
+
2. **Explicit Dependencies**: Use constructor parameters or method arguments
|
71
|
+
3. **Avoid Global State**: Maintain state within instances or external services
|
72
|
+
4. **Use Dependency Injection**: Inject dependencies through constructors
|
@@ -0,0 +1,170 @@
|
|
1
|
+
"""
|
2
|
+
ANTI-PATTERN EXAMPLE: Global State
|
3
|
+
|
4
|
+
This module demonstrates an anti-pattern where a command uses global state
|
5
|
+
to store and share data between executions.
|
6
|
+
|
7
|
+
WARNING: This is a BAD EXAMPLE! Do not use this approach in production.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from typing import Dict, Any, Optional
|
11
|
+
|
12
|
+
from mcp_proxy_adapter import Command, SuccessResult
|
13
|
+
|
14
|
+
|
15
|
+
# Global state - an anti-pattern that creates hidden dependencies
|
16
|
+
# and makes the code hard to test and maintain
|
17
|
+
GLOBAL_STATE: Dict[str, Any] = {}
|
18
|
+
|
19
|
+
|
20
|
+
class StateResult(SuccessResult):
|
21
|
+
"""Result of state command."""
|
22
|
+
|
23
|
+
def __init__(self, key: str, value: Any):
|
24
|
+
"""
|
25
|
+
Initialize result.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
key: The key accessed
|
29
|
+
value: The value retrieved
|
30
|
+
"""
|
31
|
+
self.key = key
|
32
|
+
self.value = value
|
33
|
+
|
34
|
+
def to_dict(self) -> Dict[str, Any]:
|
35
|
+
"""Convert result to dictionary."""
|
36
|
+
return {
|
37
|
+
"key": self.key,
|
38
|
+
"value": self.value
|
39
|
+
}
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def get_schema(cls) -> Dict[str, Any]:
|
43
|
+
"""Get JSON schema for result."""
|
44
|
+
return {
|
45
|
+
"type": "object",
|
46
|
+
"properties": {
|
47
|
+
"key": {"type": "string"},
|
48
|
+
"value": {"type": "string"}
|
49
|
+
},
|
50
|
+
"required": ["key"]
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
class GlobalStateCommand(Command):
|
55
|
+
"""
|
56
|
+
ANTI-PATTERN: This command uses global state.
|
57
|
+
|
58
|
+
It stores and retrieves values from a global dictionary,
|
59
|
+
creating hidden dependencies and making the code:
|
60
|
+
- Not thread-safe
|
61
|
+
- Hard to test
|
62
|
+
- Prone to unexpected behavior
|
63
|
+
- Difficult to reason about
|
64
|
+
"""
|
65
|
+
|
66
|
+
name = "global_state"
|
67
|
+
result_class = StateResult
|
68
|
+
|
69
|
+
async def execute(self, key: str, value: Any = None) -> StateResult:
|
70
|
+
"""
|
71
|
+
Execute global state command.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
key: Key to get or set
|
75
|
+
value: Value to set (if not None)
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
Result with key and current value
|
79
|
+
"""
|
80
|
+
# Get or set value in global state
|
81
|
+
if value is not None:
|
82
|
+
# Set the value in the global state
|
83
|
+
GLOBAL_STATE[key] = value
|
84
|
+
|
85
|
+
# Return the current value (which might be None if the key doesn't exist)
|
86
|
+
current_value = GLOBAL_STATE.get(key)
|
87
|
+
return StateResult(key, current_value)
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def get_schema(cls) -> Dict[str, Any]:
|
91
|
+
"""Get JSON schema for command parameters."""
|
92
|
+
return {
|
93
|
+
"type": "object",
|
94
|
+
"properties": {
|
95
|
+
"key": {
|
96
|
+
"type": "string",
|
97
|
+
"description": "Key to get or set"
|
98
|
+
},
|
99
|
+
"value": {
|
100
|
+
"type": ["string", "number", "boolean", "null", "array", "object"],
|
101
|
+
"description": "Value to set (optional)"
|
102
|
+
}
|
103
|
+
},
|
104
|
+
"required": ["key"]
|
105
|
+
}
|
106
|
+
|
107
|
+
|
108
|
+
# Even worse: Command with mixed global and instance state
|
109
|
+
class MixedStateCommand(Command):
|
110
|
+
"""
|
111
|
+
ANTI-PATTERN: This command mixes global and instance state.
|
112
|
+
|
113
|
+
It's even worse than just global state because it creates confusion
|
114
|
+
about where state is stored and how it's accessed.
|
115
|
+
"""
|
116
|
+
|
117
|
+
name = "mixed_state"
|
118
|
+
result_class = StateResult
|
119
|
+
|
120
|
+
def __init__(self):
|
121
|
+
"""Initialize with instance-specific counter."""
|
122
|
+
self._counter = 0 # Instance state
|
123
|
+
|
124
|
+
async def execute(self, key: str, value: Any = None, increment: bool = False) -> StateResult:
|
125
|
+
"""
|
126
|
+
Execute mixed state command.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
key: Key to get or set
|
130
|
+
value: Value to set (if not None)
|
131
|
+
increment: Whether to increment the instance counter
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
Result with key and current value
|
135
|
+
"""
|
136
|
+
# Instance state modification
|
137
|
+
if increment:
|
138
|
+
self._counter += 1
|
139
|
+
|
140
|
+
# Global state modification - mixing different state types!
|
141
|
+
if value is not None:
|
142
|
+
# Set in global state
|
143
|
+
GLOBAL_STATE[key] = value
|
144
|
+
|
145
|
+
# Mix instance state with the value
|
146
|
+
if self._counter > 0:
|
147
|
+
# Modify value based on instance state
|
148
|
+
if isinstance(GLOBAL_STATE.get(key), (int, float)):
|
149
|
+
GLOBAL_STATE[key] = GLOBAL_STATE.get(key, 0) + self._counter
|
150
|
+
|
151
|
+
# Return the current value (which might be None if the key doesn't exist)
|
152
|
+
current_value = GLOBAL_STATE.get(key)
|
153
|
+
return StateResult(key, current_value)
|
154
|
+
|
155
|
+
|
156
|
+
# Example of how this causes problems:
|
157
|
+
# Both commands modify the same global state, which creates hidden dependencies
|
158
|
+
# and makes behavior unpredictable, especially with concurrent requests.
|
159
|
+
|
160
|
+
# Usage example (don't do this!):
|
161
|
+
#
|
162
|
+
# # Command 1 sets a value
|
163
|
+
# await GlobalStateCommand().execute("user_count", 10)
|
164
|
+
#
|
165
|
+
# # Command 2 also modifies the same value
|
166
|
+
# await MixedStateCommand().execute("user_count", 5)
|
167
|
+
#
|
168
|
+
# # Command 1 reads the value but gets a result it didn't expect
|
169
|
+
# result = await GlobalStateCommand().execute("user_count")
|
170
|
+
# # result.value might be 5 instead of 10!
|
@@ -0,0 +1,272 @@
|
|
1
|
+
"""
|
2
|
+
ANTI-PATTERN EXAMPLE: Monolithic Command
|
3
|
+
|
4
|
+
This module demonstrates an anti-pattern where a command tries to do too much,
|
5
|
+
violating the Single Responsibility Principle.
|
6
|
+
|
7
|
+
WARNING: This is a BAD EXAMPLE! Do not use this approach in production.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import os
|
11
|
+
import json
|
12
|
+
import sqlite3
|
13
|
+
import smtplib
|
14
|
+
import time
|
15
|
+
from email.message import EmailMessage
|
16
|
+
from typing import Dict, Any, List, Optional
|
17
|
+
|
18
|
+
from mcp_proxy_adapter import Command, SuccessResult
|
19
|
+
|
20
|
+
|
21
|
+
# Global connection pool - another anti-pattern
|
22
|
+
DB_CONNECTIONS = {}
|
23
|
+
|
24
|
+
|
25
|
+
class MonolithicResult(SuccessResult):
|
26
|
+
"""Result of monolithic command."""
|
27
|
+
|
28
|
+
def __init__(self, status: str, data: Dict[str, Any]):
|
29
|
+
"""
|
30
|
+
Initialize result.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
status: Operation status
|
34
|
+
data: Result data
|
35
|
+
"""
|
36
|
+
self.status = status
|
37
|
+
self.data = data
|
38
|
+
|
39
|
+
def to_dict(self) -> Dict[str, Any]:
|
40
|
+
"""Convert result to dictionary."""
|
41
|
+
return {
|
42
|
+
"status": self.status,
|
43
|
+
"data": self.data
|
44
|
+
}
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def get_schema(cls) -> Dict[str, Any]:
|
48
|
+
"""Get JSON schema for result."""
|
49
|
+
return {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {
|
52
|
+
"status": {"type": "string"},
|
53
|
+
"data": {"type": "object"}
|
54
|
+
},
|
55
|
+
"required": ["status", "data"]
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
class MonolithicCommand(Command):
|
60
|
+
"""
|
61
|
+
ANTI-PATTERN: This command tries to do too much.
|
62
|
+
|
63
|
+
It handles:
|
64
|
+
1. Data processing
|
65
|
+
2. Validation
|
66
|
+
3. Database operations
|
67
|
+
4. File operations
|
68
|
+
5. Notifications
|
69
|
+
|
70
|
+
This violates the Single Responsibility Principle and makes the code:
|
71
|
+
- Hard to test
|
72
|
+
- Hard to maintain
|
73
|
+
- Difficult to reuse
|
74
|
+
"""
|
75
|
+
|
76
|
+
name = "do_everything"
|
77
|
+
result_class = MonolithicResult
|
78
|
+
|
79
|
+
async def execute(
|
80
|
+
self,
|
81
|
+
action: str,
|
82
|
+
data: Dict[str, Any],
|
83
|
+
save_to_db: bool = True,
|
84
|
+
generate_report: bool = True,
|
85
|
+
notify_users: List[str] = None
|
86
|
+
) -> MonolithicResult:
|
87
|
+
"""
|
88
|
+
Execute monolithic command.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
action: Action to perform (process, update, delete)
|
92
|
+
data: Input data to process
|
93
|
+
save_to_db: Whether to save data to database
|
94
|
+
generate_report: Whether to generate a report
|
95
|
+
notify_users: List of users to notify
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
Result of operations
|
99
|
+
"""
|
100
|
+
result_data = {}
|
101
|
+
|
102
|
+
# Step 1: Process data - this should be a separate command
|
103
|
+
processed_data = self._process_data(action, data)
|
104
|
+
result_data["processed"] = processed_data
|
105
|
+
|
106
|
+
# Step 2: Save to database - this should be a separate command
|
107
|
+
if save_to_db:
|
108
|
+
db_result = self._save_to_database(processed_data)
|
109
|
+
result_data["database"] = db_result
|
110
|
+
|
111
|
+
# Step 3: Generate report - this should be a separate command
|
112
|
+
if generate_report:
|
113
|
+
report_path = self._generate_report(processed_data)
|
114
|
+
result_data["report"] = report_path
|
115
|
+
|
116
|
+
# Step 4: Notify users - this should be a separate command
|
117
|
+
if notify_users:
|
118
|
+
notification_result = self._send_notifications(
|
119
|
+
notify_users, processed_data, report_path if generate_report else None
|
120
|
+
)
|
121
|
+
result_data["notifications"] = notification_result
|
122
|
+
|
123
|
+
return MonolithicResult("success", result_data)
|
124
|
+
|
125
|
+
def _process_data(self, action: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
126
|
+
"""Process input data based on action."""
|
127
|
+
# Simulate data processing
|
128
|
+
processed = dict(data)
|
129
|
+
|
130
|
+
if action == "process":
|
131
|
+
# Complex data processing logic
|
132
|
+
processed["status"] = "processed"
|
133
|
+
processed["timestamp"] = time.time()
|
134
|
+
|
135
|
+
# Calculate some values
|
136
|
+
if "values" in processed and isinstance(processed["values"], list):
|
137
|
+
processed["sum"] = sum(processed["values"])
|
138
|
+
processed["average"] = sum(processed["values"]) / len(processed["values"])
|
139
|
+
|
140
|
+
elif action == "update":
|
141
|
+
# Update logic
|
142
|
+
processed["status"] = "updated"
|
143
|
+
processed["updated_at"] = time.time()
|
144
|
+
|
145
|
+
elif action == "delete":
|
146
|
+
# Delete logic
|
147
|
+
processed["status"] = "deleted"
|
148
|
+
processed["deleted_at"] = time.time()
|
149
|
+
|
150
|
+
return processed
|
151
|
+
|
152
|
+
def _save_to_database(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
153
|
+
"""Save data to database - should be a separate command."""
|
154
|
+
# Get or create database connection
|
155
|
+
conn = self._get_db_connection()
|
156
|
+
cursor = conn.cursor()
|
157
|
+
|
158
|
+
# Create table if not exists
|
159
|
+
cursor.execute("""
|
160
|
+
CREATE TABLE IF NOT EXISTS items (
|
161
|
+
id INTEGER PRIMARY KEY,
|
162
|
+
data TEXT,
|
163
|
+
status TEXT,
|
164
|
+
timestamp REAL
|
165
|
+
)
|
166
|
+
""")
|
167
|
+
|
168
|
+
# Insert data
|
169
|
+
data_json = json.dumps(data)
|
170
|
+
cursor.execute(
|
171
|
+
"INSERT INTO items (data, status, timestamp) VALUES (?, ?, ?)",
|
172
|
+
(data_json, data.get("status", "unknown"), time.time())
|
173
|
+
)
|
174
|
+
|
175
|
+
# Get ID of inserted row
|
176
|
+
row_id = cursor.lastrowid
|
177
|
+
conn.commit()
|
178
|
+
|
179
|
+
return {"id": row_id, "status": "saved"}
|
180
|
+
|
181
|
+
def _generate_report(self, data: Dict[str, Any]) -> str:
|
182
|
+
"""Generate report file - should be a separate command."""
|
183
|
+
# Create reports directory if not exists
|
184
|
+
os.makedirs("reports", exist_ok=True)
|
185
|
+
|
186
|
+
# Generate report filename
|
187
|
+
filename = f"reports/report_{int(time.time())}.json"
|
188
|
+
|
189
|
+
# Write report to file
|
190
|
+
with open(filename, "w") as f:
|
191
|
+
json.dump(data, f, indent=2)
|
192
|
+
|
193
|
+
return filename
|
194
|
+
|
195
|
+
def _send_notifications(
|
196
|
+
self,
|
197
|
+
recipients: List[str],
|
198
|
+
data: Dict[str, Any],
|
199
|
+
report_path: Optional[str] = None
|
200
|
+
) -> Dict[str, Any]:
|
201
|
+
"""Send notifications to users - should be a separate command."""
|
202
|
+
# Configure email server (hardcoded values - another anti-pattern)
|
203
|
+
smtp_server = "smtp.example.com"
|
204
|
+
smtp_port = 587
|
205
|
+
smtp_user = "service@example.com"
|
206
|
+
smtp_password = "password123" # Hardcoding passwords is a security issue!
|
207
|
+
|
208
|
+
# Create message
|
209
|
+
msg = EmailMessage()
|
210
|
+
msg["Subject"] = f"Data {data.get('status', 'processed')}"
|
211
|
+
msg["From"] = "service@example.com"
|
212
|
+
msg["To"] = ", ".join(recipients)
|
213
|
+
|
214
|
+
# Set content
|
215
|
+
content = f"Data has been {data.get('status', 'processed')}.\n\n"
|
216
|
+
if report_path:
|
217
|
+
content += f"Report is available at: {report_path}\n"
|
218
|
+
msg.set_content(content)
|
219
|
+
|
220
|
+
# Send email (commented out to prevent actual sending)
|
221
|
+
# with smtplib.SMTP(smtp_server, smtp_port) as server:
|
222
|
+
# server.login(smtp_user, smtp_password)
|
223
|
+
# server.send_message(msg)
|
224
|
+
|
225
|
+
# Simulate successful sending
|
226
|
+
return {
|
227
|
+
"sent_to": recipients,
|
228
|
+
"status": "sent"
|
229
|
+
}
|
230
|
+
|
231
|
+
def _get_db_connection(self) -> sqlite3.Connection:
|
232
|
+
"""Get database connection from pool."""
|
233
|
+
if "default" not in DB_CONNECTIONS:
|
234
|
+
# Create new connection
|
235
|
+
DB_CONNECTIONS["default"] = sqlite3.connect(":memory:")
|
236
|
+
|
237
|
+
return DB_CONNECTIONS["default"]
|
238
|
+
|
239
|
+
@classmethod
|
240
|
+
def get_schema(cls) -> Dict[str, Any]:
|
241
|
+
"""Get JSON schema for command parameters."""
|
242
|
+
return {
|
243
|
+
"type": "object",
|
244
|
+
"properties": {
|
245
|
+
"action": {
|
246
|
+
"type": "string",
|
247
|
+
"enum": ["process", "update", "delete"],
|
248
|
+
"description": "Action to perform"
|
249
|
+
},
|
250
|
+
"data": {
|
251
|
+
"type": "object",
|
252
|
+
"description": "Input data to process"
|
253
|
+
},
|
254
|
+
"save_to_db": {
|
255
|
+
"type": "boolean",
|
256
|
+
"description": "Whether to save data to database",
|
257
|
+
"default": True
|
258
|
+
},
|
259
|
+
"generate_report": {
|
260
|
+
"type": "boolean",
|
261
|
+
"description": "Whether to generate a report",
|
262
|
+
"default": True
|
263
|
+
},
|
264
|
+
"notify_users": {
|
265
|
+
"type": "array",
|
266
|
+
"items": {"type": "string"},
|
267
|
+
"description": "List of users to notify",
|
268
|
+
"default": []
|
269
|
+
}
|
270
|
+
},
|
271
|
+
"required": ["action", "data"]
|
272
|
+
}
|