mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.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.
- mcp_proxy_adapter/api/app.py +65 -27
- mcp_proxy_adapter/api/handlers.py +1 -1
- mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
- mcp_proxy_adapter/api/tool_integration.py +5 -2
- mcp_proxy_adapter/api/tools.py +3 -3
- mcp_proxy_adapter/commands/base.py +19 -1
- mcp_proxy_adapter/commands/command_registry.py +243 -6
- mcp_proxy_adapter/commands/hooks.py +260 -0
- mcp_proxy_adapter/commands/reload_command.py +211 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
- mcp_proxy_adapter/commands/settings_command.py +189 -0
- mcp_proxy_adapter/config.py +16 -1
- mcp_proxy_adapter/core/__init__.py +44 -0
- mcp_proxy_adapter/core/logging.py +87 -34
- mcp_proxy_adapter/core/settings.py +376 -0
- mcp_proxy_adapter/core/utils.py +2 -2
- mcp_proxy_adapter/custom_openapi.py +81 -2
- mcp_proxy_adapter/examples/README.md +124 -0
- mcp_proxy_adapter/examples/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/README.md +60 -0
- mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
- mcp_proxy_adapter/examples/basic_server/config.json +35 -0
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
- mcp_proxy_adapter/examples/basic_server/server.py +98 -0
- mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
- mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
- mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
- mcp_proxy_adapter/examples/deployment/README.md +49 -0
- mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
- mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
- {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
- mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
- mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
- mcp_proxy_adapter/examples/deployment/run.sh +43 -0
- mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
- mcp_proxy_adapter/openapi.py +3 -2
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
- mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
- mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
- mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-4.0.0.dist-info/RECORD +110 -0
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/WHEEL +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/top_level.txt +0 -1
- examples/__init__.py +0 -19
- examples/anti_patterns/README.md +0 -51
- examples/anti_patterns/__init__.py +0 -9
- examples/anti_patterns/bad_design/README.md +0 -72
- examples/anti_patterns/bad_design/global_state.py +0 -170
- examples/anti_patterns/bad_design/monolithic_command.py +0 -272
- examples/basic_example/README.md +0 -245
- examples/basic_example/__init__.py +0 -8
- examples/basic_example/commands/__init__.py +0 -5
- examples/basic_example/commands/echo_command.py +0 -95
- examples/basic_example/commands/math_command.py +0 -151
- examples/basic_example/commands/time_command.py +0 -152
- examples/basic_example/docs/EN/README.md +0 -177
- examples/basic_example/docs/RU/README.md +0 -177
- examples/basic_example/server.py +0 -151
- examples/basic_example/tests/conftest.py +0 -243
- examples/check_vstl_schema.py +0 -106
- examples/commands/echo_command.py +0 -52
- examples/commands/echo_command_di.py +0 -152
- examples/commands/echo_result.py +0 -65
- examples/commands/get_date_command.py +0 -98
- examples/commands/new_uuid4_command.py +0 -91
- examples/complete_example/Dockerfile +0 -24
- examples/complete_example/README.md +0 -92
- examples/complete_example/__init__.py +0 -8
- examples/complete_example/commands/__init__.py +0 -5
- examples/complete_example/commands/system_command.py +0 -328
- examples/complete_example/config.json +0 -41
- examples/complete_example/configs/config.dev.yaml +0 -40
- examples/complete_example/configs/config.docker.yaml +0 -40
- examples/complete_example/docker-compose.yml +0 -35
- examples/complete_example/requirements.txt +0 -20
- examples/complete_example/server.py +0 -113
- examples/di_example/.pytest_cache/README.md +0 -8
- examples/di_example/server.py +0 -249
- examples/fix_vstl_help.py +0 -123
- examples/minimal_example/README.md +0 -65
- examples/minimal_example/__init__.py +0 -8
- examples/minimal_example/config.json +0 -14
- examples/minimal_example/main.py +0 -136
- examples/minimal_example/simple_server.py +0 -163
- examples/minimal_example/tests/conftest.py +0 -171
- examples/minimal_example/tests/test_hello_command.py +0 -111
- examples/minimal_example/tests/test_integration.py +0 -181
- examples/patch_vstl_service.py +0 -105
- examples/patch_vstl_service_mcp.py +0 -108
- examples/server.py +0 -69
- examples/simple_server.py +0 -128
- examples/test_package_3.1.4.py +0 -177
- examples/test_server.py +0 -134
- examples/tool_description_example.py +0 -82
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
"""
|
2
|
+
Settings command for demonstrating configuration management.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Optional
|
6
|
+
from mcp_proxy_adapter.commands.base import Command
|
7
|
+
from mcp_proxy_adapter.core.settings import Settings, get_setting, set_setting, reload_settings
|
8
|
+
|
9
|
+
|
10
|
+
class SettingsResult:
|
11
|
+
"""Result class for settings command."""
|
12
|
+
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
success: bool,
|
16
|
+
operation: str,
|
17
|
+
key: Optional[str] = None,
|
18
|
+
value: Any = None,
|
19
|
+
all_settings: Optional[Dict[str, Any]] = None,
|
20
|
+
error_message: Optional[str] = None
|
21
|
+
):
|
22
|
+
self.success = success
|
23
|
+
self.operation = operation
|
24
|
+
self.key = key
|
25
|
+
self.value = value
|
26
|
+
self.all_settings = all_settings
|
27
|
+
self.error_message = error_message
|
28
|
+
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
30
|
+
"""Convert result to dictionary."""
|
31
|
+
result = {
|
32
|
+
"success": self.success,
|
33
|
+
"operation": self.operation
|
34
|
+
}
|
35
|
+
|
36
|
+
if self.key is not None:
|
37
|
+
result["key"] = self.key
|
38
|
+
if self.value is not None:
|
39
|
+
result["value"] = self.value
|
40
|
+
if self.all_settings is not None:
|
41
|
+
result["all_settings"] = self.all_settings
|
42
|
+
if self.error_message is not None:
|
43
|
+
result["error_message"] = self.error_message
|
44
|
+
|
45
|
+
return result
|
46
|
+
|
47
|
+
def get_schema(self) -> Dict[str, Any]:
|
48
|
+
"""Get schema for the result."""
|
49
|
+
return {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {
|
52
|
+
"success": {
|
53
|
+
"type": "boolean",
|
54
|
+
"description": "Whether the operation was successful"
|
55
|
+
},
|
56
|
+
"operation": {
|
57
|
+
"type": "string",
|
58
|
+
"description": "Type of operation performed",
|
59
|
+
"enum": ["get", "set", "get_all", "reload"]
|
60
|
+
},
|
61
|
+
"key": {
|
62
|
+
"type": "string",
|
63
|
+
"description": "Configuration key (for get/set operations)"
|
64
|
+
},
|
65
|
+
"value": {
|
66
|
+
"description": "Configuration value (for get/set operations)"
|
67
|
+
},
|
68
|
+
"all_settings": {
|
69
|
+
"type": "object",
|
70
|
+
"description": "All configuration settings (for get_all operation)"
|
71
|
+
},
|
72
|
+
"error_message": {
|
73
|
+
"type": "string",
|
74
|
+
"description": "Error message if operation failed"
|
75
|
+
}
|
76
|
+
},
|
77
|
+
"required": ["success", "operation"]
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
class SettingsCommand(Command):
|
82
|
+
"""Command for managing framework settings."""
|
83
|
+
|
84
|
+
name = "settings"
|
85
|
+
description = "Manage framework settings and configuration"
|
86
|
+
|
87
|
+
async def execute(self, **params) -> SettingsResult:
|
88
|
+
"""
|
89
|
+
Execute settings command.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
operation: Operation to perform (get, set, get_all, reload)
|
93
|
+
key: Configuration key (for get/set operations)
|
94
|
+
value: Configuration value (for set operation)
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
SettingsResult with operation result
|
98
|
+
"""
|
99
|
+
try:
|
100
|
+
operation = params.get("operation", "get_all")
|
101
|
+
|
102
|
+
if operation == "get":
|
103
|
+
key = params.get("key")
|
104
|
+
if not key:
|
105
|
+
return SettingsResult(
|
106
|
+
success=False,
|
107
|
+
operation=operation,
|
108
|
+
error_message="Key is required for 'get' operation"
|
109
|
+
)
|
110
|
+
|
111
|
+
value = get_setting(key)
|
112
|
+
return SettingsResult(
|
113
|
+
success=True,
|
114
|
+
operation=operation,
|
115
|
+
key=key,
|
116
|
+
value=value
|
117
|
+
)
|
118
|
+
|
119
|
+
elif operation == "set":
|
120
|
+
key = params.get("key")
|
121
|
+
value = params.get("value")
|
122
|
+
|
123
|
+
if not key:
|
124
|
+
return SettingsResult(
|
125
|
+
success=False,
|
126
|
+
operation=operation,
|
127
|
+
error_message="Key is required for 'set' operation"
|
128
|
+
)
|
129
|
+
|
130
|
+
set_setting(key, value)
|
131
|
+
return SettingsResult(
|
132
|
+
success=True,
|
133
|
+
operation=operation,
|
134
|
+
key=key,
|
135
|
+
value=value
|
136
|
+
)
|
137
|
+
|
138
|
+
elif operation == "get_all":
|
139
|
+
all_settings = Settings.get_all_settings()
|
140
|
+
return SettingsResult(
|
141
|
+
success=True,
|
142
|
+
operation=operation,
|
143
|
+
all_settings=all_settings
|
144
|
+
)
|
145
|
+
|
146
|
+
elif operation == "reload":
|
147
|
+
reload_settings()
|
148
|
+
return SettingsResult(
|
149
|
+
success=True,
|
150
|
+
operation=operation
|
151
|
+
)
|
152
|
+
|
153
|
+
else:
|
154
|
+
return SettingsResult(
|
155
|
+
success=False,
|
156
|
+
operation=operation,
|
157
|
+
error_message=f"Unknown operation: {operation}. Supported operations: get, set, get_all, reload"
|
158
|
+
)
|
159
|
+
|
160
|
+
except Exception as e:
|
161
|
+
return SettingsResult(
|
162
|
+
success=False,
|
163
|
+
operation=params.get("operation", "unknown"),
|
164
|
+
error_message=str(e)
|
165
|
+
)
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
def get_schema(cls) -> Dict[str, Any]:
|
169
|
+
"""Get schema for the command."""
|
170
|
+
return {
|
171
|
+
"type": "object",
|
172
|
+
"properties": {
|
173
|
+
"operation": {
|
174
|
+
"type": "string",
|
175
|
+
"description": "Operation to perform",
|
176
|
+
"enum": ["get", "set", "get_all", "reload"],
|
177
|
+
"default": "get_all"
|
178
|
+
},
|
179
|
+
"key": {
|
180
|
+
"type": "string",
|
181
|
+
"description": "Configuration key in dot notation (e.g., 'server.host', 'custom.feature_enabled')"
|
182
|
+
},
|
183
|
+
"value": {
|
184
|
+
"description": "Configuration value to set (for 'set' operation)"
|
185
|
+
}
|
186
|
+
},
|
187
|
+
"required": ["operation"],
|
188
|
+
"additionalProperties": False
|
189
|
+
}
|
mcp_proxy_adapter/config.py
CHANGED
@@ -39,7 +39,22 @@ class Config:
|
|
39
39
|
},
|
40
40
|
"logging": {
|
41
41
|
"level": "INFO",
|
42
|
-
"file": None
|
42
|
+
"file": None,
|
43
|
+
"log_dir": "./logs",
|
44
|
+
"log_file": "mcp_proxy_adapter.log",
|
45
|
+
"error_log_file": "mcp_proxy_adapter_error.log",
|
46
|
+
"access_log_file": "mcp_proxy_adapter_access.log",
|
47
|
+
"max_file_size": "10MB",
|
48
|
+
"backup_count": 5,
|
49
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
50
|
+
"date_format": "%Y-%m-%d %H:%M:%S",
|
51
|
+
"console_output": True,
|
52
|
+
"file_output": True
|
53
|
+
},
|
54
|
+
"commands": {
|
55
|
+
"auto_discovery": True,
|
56
|
+
"discovery_path": "mcp_proxy_adapter.commands",
|
57
|
+
"custom_commands_path": None
|
43
58
|
}
|
44
59
|
}
|
45
60
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"""
|
2
|
+
Core functionality for MCP Proxy Adapter.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .errors import *
|
6
|
+
from .logging import *
|
7
|
+
from .settings import *
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
# Errors
|
11
|
+
"NotFoundError",
|
12
|
+
"InvalidParamsError",
|
13
|
+
"CommandExecutionError",
|
14
|
+
"ConfigurationError",
|
15
|
+
|
16
|
+
# Logging
|
17
|
+
"setup_logging",
|
18
|
+
"get_logger",
|
19
|
+
"logger",
|
20
|
+
"RequestLogger",
|
21
|
+
"CustomFormatter",
|
22
|
+
"RequestContextFilter",
|
23
|
+
|
24
|
+
# Settings
|
25
|
+
"Settings",
|
26
|
+
"ServerSettings",
|
27
|
+
"LoggingSettings",
|
28
|
+
"CommandsSettings",
|
29
|
+
"get_server_host",
|
30
|
+
"get_server_port",
|
31
|
+
"get_server_debug",
|
32
|
+
"get_logging_level",
|
33
|
+
"get_logging_dir",
|
34
|
+
"get_auto_discovery",
|
35
|
+
"get_discovery_path",
|
36
|
+
"get_setting",
|
37
|
+
"set_setting",
|
38
|
+
"reload_settings",
|
39
|
+
"add_custom_settings",
|
40
|
+
"get_custom_settings",
|
41
|
+
"get_custom_setting_value",
|
42
|
+
"set_custom_setting_value",
|
43
|
+
"clear_custom_settings"
|
44
|
+
]
|
@@ -122,15 +122,28 @@ def setup_logging(
|
|
122
122
|
# Get parameters from configuration if not explicitly specified
|
123
123
|
level = level or config.get("logging.level", "INFO")
|
124
124
|
log_file = log_file or config.get("logging.file")
|
125
|
-
rotation_type = rotation_type or
|
125
|
+
rotation_type = rotation_type or "size" # Default to size-based rotation
|
126
126
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
127
|
+
# Get log directory and file settings from config
|
128
|
+
log_dir = config.get("logging.log_dir", "./logs")
|
129
|
+
log_file_name = config.get("logging.log_file", "mcp_proxy_adapter.log")
|
130
|
+
error_log_file = config.get("logging.error_log_file", "mcp_proxy_adapter_error.log")
|
131
|
+
access_log_file = config.get("logging.access_log_file", "mcp_proxy_adapter_access.log")
|
130
132
|
|
131
|
-
#
|
132
|
-
|
133
|
-
|
133
|
+
# Get rotation settings from config
|
134
|
+
max_file_size_str = config.get("logging.max_file_size", "10MB")
|
135
|
+
backup_count = backup_count or config.get("logging.backup_count", 5)
|
136
|
+
|
137
|
+
# Parse max file size (e.g., "10MB" -> 10 * 1024 * 1024)
|
138
|
+
max_bytes = max_bytes or _parse_file_size(max_file_size_str)
|
139
|
+
|
140
|
+
# Get format settings
|
141
|
+
log_format = config.get("logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
142
|
+
date_format = config.get("logging.date_format", "%Y-%m-%d %H:%M:%S")
|
143
|
+
|
144
|
+
# Get output settings
|
145
|
+
console_output = config.get("logging.console_output", True)
|
146
|
+
file_output = config.get("logging.file_output", True)
|
134
147
|
|
135
148
|
# Convert string logging level to constant
|
136
149
|
numeric_level = getattr(logging, level.upper(), None)
|
@@ -142,42 +155,60 @@ def setup_logging(
|
|
142
155
|
logger.setLevel(numeric_level)
|
143
156
|
logger.handlers = [] # Clear handlers in case of repeated call
|
144
157
|
|
145
|
-
# Create
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
# Create formatter
|
159
|
+
formatter = logging.Formatter(log_format, date_format)
|
160
|
+
|
161
|
+
# Create console handler if enabled
|
162
|
+
if console_output:
|
163
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
164
|
+
console_handler.setLevel(numeric_level)
|
165
|
+
console_handler.setFormatter(CustomFormatter())
|
166
|
+
logger.addHandler(console_handler)
|
167
|
+
|
168
|
+
# Create file handlers if file output is enabled
|
169
|
+
if file_output and log_dir:
|
170
|
+
# Create directory for log files if it doesn't exist
|
171
|
+
if not os.path.exists(log_dir):
|
156
172
|
os.makedirs(log_dir, exist_ok=True)
|
157
173
|
|
158
|
-
#
|
159
|
-
if
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
174
|
+
# Main log file
|
175
|
+
if log_file_name:
|
176
|
+
main_log_path = os.path.join(log_dir, log_file_name)
|
177
|
+
main_handler = RotatingFileHandler(
|
178
|
+
main_log_path,
|
179
|
+
maxBytes=max_bytes,
|
180
|
+
backupCount=backup_count,
|
181
|
+
encoding="utf-8"
|
182
|
+
)
|
183
|
+
main_handler.setLevel(numeric_level)
|
184
|
+
main_handler.setFormatter(formatter)
|
185
|
+
logger.addHandler(main_handler)
|
186
|
+
|
187
|
+
# Error log file
|
188
|
+
if error_log_file:
|
189
|
+
error_log_path = os.path.join(log_dir, error_log_file)
|
190
|
+
error_handler = RotatingFileHandler(
|
191
|
+
error_log_path,
|
192
|
+
maxBytes=max_bytes,
|
164
193
|
backupCount=backup_count,
|
165
194
|
encoding="utf-8"
|
166
195
|
)
|
167
|
-
|
168
|
-
|
169
|
-
|
196
|
+
error_handler.setLevel(logging.ERROR)
|
197
|
+
error_handler.setFormatter(formatter)
|
198
|
+
logger.addHandler(error_handler)
|
199
|
+
|
200
|
+
# Access log file (for HTTP requests)
|
201
|
+
if access_log_file:
|
202
|
+
access_log_path = os.path.join(log_dir, access_log_file)
|
203
|
+
access_handler = RotatingFileHandler(
|
204
|
+
access_log_path,
|
170
205
|
maxBytes=max_bytes,
|
171
206
|
backupCount=backup_count,
|
172
207
|
encoding="utf-8"
|
173
208
|
)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
178
|
-
)
|
179
|
-
file_handler.setFormatter(file_formatter)
|
180
|
-
logger.addHandler(file_handler)
|
209
|
+
access_handler.setLevel(logging.INFO)
|
210
|
+
access_handler.setFormatter(formatter)
|
211
|
+
logger.addHandler(access_handler)
|
181
212
|
|
182
213
|
# Configure loggers for external libraries
|
183
214
|
log_levels = config.get("logging.levels", {})
|
@@ -188,6 +219,28 @@ def setup_logging(
|
|
188
219
|
return logger
|
189
220
|
|
190
221
|
|
222
|
+
def _parse_file_size(size_str: str) -> int:
|
223
|
+
"""
|
224
|
+
Parse file size string to bytes.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
size_str: Size string (e.g., "10MB", "1GB", "100KB")
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Size in bytes
|
231
|
+
"""
|
232
|
+
size_str = size_str.upper()
|
233
|
+
if size_str.endswith("KB"):
|
234
|
+
return int(size_str[:-2]) * 1024
|
235
|
+
elif size_str.endswith("MB"):
|
236
|
+
return int(size_str[:-2]) * 1024 * 1024
|
237
|
+
elif size_str.endswith("GB"):
|
238
|
+
return int(size_str[:-2]) * 1024 * 1024 * 1024
|
239
|
+
else:
|
240
|
+
# Assume bytes if no unit specified
|
241
|
+
return int(size_str)
|
242
|
+
|
243
|
+
|
191
244
|
def get_logger(name: str) -> logging.Logger:
|
192
245
|
"""
|
193
246
|
Get a logger with the specified name.
|