agentscope-runtime 0.1.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.
- agentscope_runtime/__init__.py +4 -0
- agentscope_runtime/engine/__init__.py +9 -0
- agentscope_runtime/engine/agents/__init__.py +2 -0
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
- agentscope_runtime/engine/agents/agno_agent.py +220 -0
- agentscope_runtime/engine/agents/base_agent.py +29 -0
- agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
- agentscope_runtime/engine/agents/llm_agent.py +51 -0
- agentscope_runtime/engine/deployers/__init__.py +3 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
- agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
- agentscope_runtime/engine/deployers/base.py +17 -0
- agentscope_runtime/engine/deployers/local_deployer.py +586 -0
- agentscope_runtime/engine/helpers/helper.py +127 -0
- agentscope_runtime/engine/llms/__init__.py +3 -0
- agentscope_runtime/engine/llms/base_llm.py +60 -0
- agentscope_runtime/engine/llms/qwen_llm.py +47 -0
- agentscope_runtime/engine/misc/__init__.py +0 -0
- agentscope_runtime/engine/runner.py +186 -0
- agentscope_runtime/engine/schemas/__init__.py +0 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
- agentscope_runtime/engine/schemas/context.py +54 -0
- agentscope_runtime/engine/services/__init__.py +9 -0
- agentscope_runtime/engine/services/base.py +77 -0
- agentscope_runtime/engine/services/context_manager.py +129 -0
- agentscope_runtime/engine/services/environment_manager.py +50 -0
- agentscope_runtime/engine/services/manager.py +174 -0
- agentscope_runtime/engine/services/memory_service.py +270 -0
- agentscope_runtime/engine/services/sandbox_service.py +198 -0
- agentscope_runtime/engine/services/session_history_service.py +256 -0
- agentscope_runtime/engine/tracing/__init__.py +40 -0
- agentscope_runtime/engine/tracing/base.py +309 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
- agentscope_runtime/engine/tracing/wrapper.py +321 -0
- agentscope_runtime/sandbox/__init__.py +14 -0
- agentscope_runtime/sandbox/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
- agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
- agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
- agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
- agentscope_runtime/sandbox/box/sandbox.py +115 -0
- agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
- agentscope_runtime/sandbox/box/shared/app.py +44 -0
- agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
- agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
- agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
- agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
- agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
- agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
- agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/base.py +120 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
- agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
- agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
- agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
- agentscope_runtime/sandbox/build.py +213 -0
- agentscope_runtime/sandbox/client/__init__.py +5 -0
- agentscope_runtime/sandbox/client/http_client.py +527 -0
- agentscope_runtime/sandbox/client/training_client.py +265 -0
- agentscope_runtime/sandbox/constant.py +5 -0
- agentscope_runtime/sandbox/custom/__init__.py +16 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
- agentscope_runtime/sandbox/custom/example.py +37 -0
- agentscope_runtime/sandbox/enums.py +68 -0
- agentscope_runtime/sandbox/manager/__init__.py +4 -0
- agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
- agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
- agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
- agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
- agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
- agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
- agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
- agentscope_runtime/sandbox/manager/server/app.py +194 -0
- agentscope_runtime/sandbox/manager/server/config.py +68 -0
- agentscope_runtime/sandbox/manager/server/models.py +17 -0
- agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
- agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
- agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
- agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
- agentscope_runtime/sandbox/manager/utils.py +78 -0
- agentscope_runtime/sandbox/mcp_server.py +192 -0
- agentscope_runtime/sandbox/model/__init__.py +12 -0
- agentscope_runtime/sandbox/model/api.py +16 -0
- agentscope_runtime/sandbox/model/container.py +72 -0
- agentscope_runtime/sandbox/model/manager_config.py +158 -0
- agentscope_runtime/sandbox/registry.py +129 -0
- agentscope_runtime/sandbox/tools/__init__.py +12 -0
- agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
- agentscope_runtime/sandbox/tools/base/tool.py +52 -0
- agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
- agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
- agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
- agentscope_runtime/sandbox/tools/function_tool.py +321 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
- agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
- agentscope_runtime/sandbox/tools/tool.py +123 -0
- agentscope_runtime/sandbox/tools/utils.py +68 -0
- agentscope_runtime/version.py +2 -0
- agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
- agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
- agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
- agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
- agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
- agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=protected-access, unused-argument
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI, HTTPException, Request, Depends
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
9
|
+
from fastapi.responses import JSONResponse
|
|
10
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
11
|
+
|
|
12
|
+
from ...manager.server.config import settings
|
|
13
|
+
from ...manager.server.models import (
|
|
14
|
+
ErrorResponse,
|
|
15
|
+
HealthResponse,
|
|
16
|
+
)
|
|
17
|
+
from ...manager.sandbox_manager import SandboxManager
|
|
18
|
+
from ...model.manager_config import SandboxManagerEnvConfig
|
|
19
|
+
from ....version import __version__
|
|
20
|
+
|
|
21
|
+
# Configure logging
|
|
22
|
+
logging.basicConfig(level=logging.INFO)
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Create FastAPI app
|
|
26
|
+
app = FastAPI(
|
|
27
|
+
title="Runtime Manager Service",
|
|
28
|
+
description="Service for managing runtime containers",
|
|
29
|
+
version=__version__,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Add CORS middleware
|
|
33
|
+
app.add_middleware(
|
|
34
|
+
CORSMiddleware,
|
|
35
|
+
allow_origins=["*"],
|
|
36
|
+
allow_credentials=True,
|
|
37
|
+
allow_methods=["*"],
|
|
38
|
+
allow_headers=["*"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Security scheme
|
|
42
|
+
security = HTTPBearer(auto_error=False)
|
|
43
|
+
|
|
44
|
+
# Global SandboxManager instance
|
|
45
|
+
_runtime_manager = None
|
|
46
|
+
_config = SandboxManagerEnvConfig(
|
|
47
|
+
container_prefix_key=settings.CONTAINER_PREFIX_KEY,
|
|
48
|
+
file_system=settings.FILE_SYSTEM,
|
|
49
|
+
redis_enabled=settings.REDIS_ENABLED,
|
|
50
|
+
container_deployment=settings.CONTAINER_DEPLOYMENT,
|
|
51
|
+
default_mount_dir=settings.DEFAULT_MOUNT_DIR,
|
|
52
|
+
storage_folder=settings.STORAGE_FOLDER,
|
|
53
|
+
port_range=settings.PORT_RANGE,
|
|
54
|
+
pool_size=settings.POOL_SIZE,
|
|
55
|
+
oss_endpoint=settings.OSS_ENDPOINT,
|
|
56
|
+
oss_access_key_id=settings.OSS_ACCESS_KEY_ID,
|
|
57
|
+
oss_access_key_secret=settings.OSS_ACCESS_KEY_SECRET,
|
|
58
|
+
oss_bucket_name=settings.OSS_BUCKET_NAME,
|
|
59
|
+
redis_server=settings.REDIS_SERVER,
|
|
60
|
+
redis_port=settings.REDIS_PORT,
|
|
61
|
+
redis_db=settings.REDIS_DB,
|
|
62
|
+
redis_user=settings.REDIS_USER,
|
|
63
|
+
redis_password=settings.REDIS_PASSWORD,
|
|
64
|
+
redis_port_key=settings.REDIS_PORT_KEY,
|
|
65
|
+
redis_container_pool_key=settings.REDIS_CONTAINER_POOL_KEY,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def verify_token(
|
|
70
|
+
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
71
|
+
):
|
|
72
|
+
"""Verify Bearer token"""
|
|
73
|
+
if not hasattr(settings, "BEARER_TOKEN") or not settings.BEARER_TOKEN:
|
|
74
|
+
logger.warning("BEARER_TOKEN not configured, skipping authentication")
|
|
75
|
+
return credentials
|
|
76
|
+
|
|
77
|
+
if credentials is None:
|
|
78
|
+
logger.error("Authentication required but no token provided")
|
|
79
|
+
raise HTTPException(
|
|
80
|
+
status_code=401,
|
|
81
|
+
detail="Authentication required",
|
|
82
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if credentials.credentials != settings.BEARER_TOKEN:
|
|
86
|
+
logger.error(
|
|
87
|
+
f"Invalid token provided: {credentials.credentials[:10]}...",
|
|
88
|
+
)
|
|
89
|
+
raise HTTPException(
|
|
90
|
+
status_code=401,
|
|
91
|
+
detail="Invalid authentication credentials",
|
|
92
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
93
|
+
)
|
|
94
|
+
return credentials
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_runtime_manager():
|
|
98
|
+
"""Get or create the global SandboxManager instance"""
|
|
99
|
+
global _runtime_manager
|
|
100
|
+
if _runtime_manager is None:
|
|
101
|
+
_runtime_manager = SandboxManager(
|
|
102
|
+
config=_config,
|
|
103
|
+
default_type=settings.DEFAULT_SANDBOX_TYPE,
|
|
104
|
+
)
|
|
105
|
+
return _runtime_manager
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_endpoint(method):
|
|
109
|
+
async def endpoint(
|
|
110
|
+
request: Request,
|
|
111
|
+
token: HTTPAuthorizationCredentials = Depends(verify_token),
|
|
112
|
+
):
|
|
113
|
+
try:
|
|
114
|
+
data = await request.json()
|
|
115
|
+
logger.info(
|
|
116
|
+
f"Calling {method.__name__} with data: {data}",
|
|
117
|
+
)
|
|
118
|
+
result = method(**data)
|
|
119
|
+
if hasattr(result, "model_dump_json"):
|
|
120
|
+
return JSONResponse(content={"data": result.model_dump_json()})
|
|
121
|
+
return JSONResponse(content={"data": result})
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(
|
|
124
|
+
f"Error in {method.__name__}: {str(e)},"
|
|
125
|
+
f" {traceback.format_exc()}",
|
|
126
|
+
)
|
|
127
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
128
|
+
|
|
129
|
+
return endpoint
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def register_routes(_app, instance):
|
|
133
|
+
for _, method in inspect.getmembers(
|
|
134
|
+
instance,
|
|
135
|
+
predicate=inspect.ismethod,
|
|
136
|
+
):
|
|
137
|
+
if getattr(method, "_is_remote_wrapper", False):
|
|
138
|
+
http_method = method._http_method.lower()
|
|
139
|
+
path = method._path
|
|
140
|
+
|
|
141
|
+
endpoint = create_endpoint(method)
|
|
142
|
+
|
|
143
|
+
if http_method == "get":
|
|
144
|
+
_app.get(path)(endpoint)
|
|
145
|
+
elif http_method == "post":
|
|
146
|
+
_app.post(path)(endpoint)
|
|
147
|
+
elif http_method == "delete":
|
|
148
|
+
_app.delete(path)(endpoint)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.on_event("startup")
|
|
152
|
+
async def startup_event():
|
|
153
|
+
"""Initialize the SandboxManager on startup"""
|
|
154
|
+
get_runtime_manager()
|
|
155
|
+
register_routes(app, _runtime_manager)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.on_event("shutdown")
|
|
159
|
+
async def shutdown_event():
|
|
160
|
+
"""Cleanup resources on shutdown"""
|
|
161
|
+
global _runtime_manager
|
|
162
|
+
if _runtime_manager and settings.AUTO_CLEANUP:
|
|
163
|
+
_runtime_manager.cleanup()
|
|
164
|
+
_runtime_manager = None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@app.get(
|
|
168
|
+
"/health",
|
|
169
|
+
response_model=HealthResponse,
|
|
170
|
+
responses={500: {"model": ErrorResponse}},
|
|
171
|
+
)
|
|
172
|
+
async def health_check():
|
|
173
|
+
"""Health check endpoint"""
|
|
174
|
+
return HealthResponse(
|
|
175
|
+
status="healthy",
|
|
176
|
+
version=_runtime_manager.default_type.value,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main():
|
|
181
|
+
"""Main entry point for the Runtime Manager Service"""
|
|
182
|
+
import uvicorn
|
|
183
|
+
|
|
184
|
+
uvicorn.run(
|
|
185
|
+
"agentscope_runtime.sandbox.manager.server.app:app",
|
|
186
|
+
host=settings.HOST,
|
|
187
|
+
port=settings.PORT,
|
|
188
|
+
workers=settings.WORKERS,
|
|
189
|
+
reload=settings.DEBUG,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional, Tuple, Literal
|
|
4
|
+
from pydantic_settings import BaseSettings
|
|
5
|
+
from pydantic import field_validator
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
env_file = ".env"
|
|
9
|
+
env_example_file = ".env.example"
|
|
10
|
+
|
|
11
|
+
# Load the appropriate .env file
|
|
12
|
+
if os.path.exists(env_file):
|
|
13
|
+
load_dotenv(env_file)
|
|
14
|
+
elif os.path.exists(env_example_file):
|
|
15
|
+
load_dotenv(env_example_file)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Settings(BaseSettings):
|
|
19
|
+
"""Runtime Manager Service Settings"""
|
|
20
|
+
|
|
21
|
+
# Service settings
|
|
22
|
+
HOST: str = "127.0.0.1"
|
|
23
|
+
PORT: int = 8000
|
|
24
|
+
WORKERS: int = 1
|
|
25
|
+
DEBUG: bool = False
|
|
26
|
+
BEARER_TOKEN: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
# Runtime Manager settings
|
|
29
|
+
DEFAULT_SANDBOX_TYPE: str = "base"
|
|
30
|
+
WORKDIR: str = "/workspace"
|
|
31
|
+
POOL_SIZE: int = 1
|
|
32
|
+
AUTO_CLEANUP: bool = True
|
|
33
|
+
CONTAINER_PREFIX_KEY: str = "runtime_sandbox_container_"
|
|
34
|
+
CONTAINER_DEPLOYMENT: Literal["docker", "cloud"] = "docker"
|
|
35
|
+
DEFAULT_MOUNT_DIR: str = "sessions_mount_dir"
|
|
36
|
+
STORAGE_FOLDER: str = "runtime_sandbox_storage"
|
|
37
|
+
PORT_RANGE: Tuple[int, int] = (49152, 59152)
|
|
38
|
+
|
|
39
|
+
# Redis settings
|
|
40
|
+
REDIS_ENABLED: bool = False
|
|
41
|
+
REDIS_SERVER: str = "localhost"
|
|
42
|
+
REDIS_PORT: int = 6379
|
|
43
|
+
REDIS_DB: int = 0
|
|
44
|
+
REDIS_USER: Optional[str] = None
|
|
45
|
+
REDIS_PASSWORD: Optional[str] = None
|
|
46
|
+
REDIS_PORT_KEY: str = "_runtime_sandbox_container_occupied_ports"
|
|
47
|
+
REDIS_CONTAINER_POOL_KEY: str = "_runtime_sandbox_container_container_pool"
|
|
48
|
+
|
|
49
|
+
# OSS settings
|
|
50
|
+
FILE_SYSTEM: Literal["local", "oss"] = "local"
|
|
51
|
+
OSS_ENDPOINT: str = "http://oss-cn-hangzhou.aliyuncs.com"
|
|
52
|
+
OSS_ACCESS_KEY_ID: str = "your-access-key-id"
|
|
53
|
+
OSS_ACCESS_KEY_SECRET: str = "your-access-key-secret"
|
|
54
|
+
OSS_BUCKET_NAME: str = "your-bucket-name"
|
|
55
|
+
|
|
56
|
+
class Config:
|
|
57
|
+
env_file = env_file if os.path.exists(env_file) else env_example_file
|
|
58
|
+
case_sensitive = True
|
|
59
|
+
|
|
60
|
+
@field_validator("WORKERS", mode="before")
|
|
61
|
+
@classmethod
|
|
62
|
+
def validate_workers(cls, value, info):
|
|
63
|
+
if not info.data.get("REDIS_ENABLED", False):
|
|
64
|
+
return 1
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
settings = Settings()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ErrorResponse(BaseModel):
|
|
7
|
+
"""Error response model"""
|
|
8
|
+
|
|
9
|
+
error: str
|
|
10
|
+
detail: Optional[str] = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HealthResponse(BaseModel):
|
|
14
|
+
"""Health check response model"""
|
|
15
|
+
|
|
16
|
+
status: str
|
|
17
|
+
version: str
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import abc
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DataStorage(abc.ABC):
|
|
6
|
+
@abc.abstractmethod
|
|
7
|
+
def download_folder(self, source_path, destination_path):
|
|
8
|
+
"""Download a folder from storage to a local path."""
|
|
9
|
+
|
|
10
|
+
@abc.abstractmethod
|
|
11
|
+
def upload_folder(self, source_path, destination_path):
|
|
12
|
+
"""Upload a local folder to storage."""
|
|
13
|
+
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def path_join(self, *args):
|
|
16
|
+
"""Joins multiple path components into a single path string."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
from .data_storage import DataStorage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LocalStorage(DataStorage):
|
|
9
|
+
def download_folder(self, source_path, destination_path):
|
|
10
|
+
"""Copy a folder from source_path to destination_path."""
|
|
11
|
+
abs_source_path = os.path.abspath(source_path)
|
|
12
|
+
abs_destination_path = os.path.abspath(destination_path)
|
|
13
|
+
|
|
14
|
+
if abs_source_path == abs_destination_path:
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
if not os.path.exists(source_path):
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
# Ensure the destination path exists
|
|
21
|
+
os.makedirs(destination_path, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
# Copy the directory structure and files
|
|
24
|
+
for root, _, files in os.walk(source_path):
|
|
25
|
+
relative_path = os.path.relpath(root, source_path)
|
|
26
|
+
dest_dir = os.path.join(destination_path, relative_path)
|
|
27
|
+
|
|
28
|
+
# Ensure the destination directory exists
|
|
29
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
# Copy files
|
|
32
|
+
for file in files:
|
|
33
|
+
src_file = os.path.join(root, file)
|
|
34
|
+
dest_file = os.path.join(dest_dir, file)
|
|
35
|
+
shutil.copy2(src_file, dest_file)
|
|
36
|
+
|
|
37
|
+
def upload_folder(self, source_path, destination_path):
|
|
38
|
+
"""Copy a folder from source_path to destination_path."""
|
|
39
|
+
# This is essentially a symmetric operation of download_folder
|
|
40
|
+
self.download_folder(source_path, destination_path)
|
|
41
|
+
|
|
42
|
+
def path_join(self, *args):
|
|
43
|
+
"""Join path components."""
|
|
44
|
+
return os.path.join(*args)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import hashlib
|
|
4
|
+
import oss2
|
|
5
|
+
|
|
6
|
+
from .data_storage import DataStorage
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def calculate_md5(file_path):
|
|
10
|
+
"""Calculate the MD5 checksum of a file."""
|
|
11
|
+
with open(file_path, "rb") as f:
|
|
12
|
+
md5 = hashlib.md5()
|
|
13
|
+
while chunk := f.read(8192):
|
|
14
|
+
md5.update(chunk)
|
|
15
|
+
return md5.hexdigest()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OSSStorage(DataStorage):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
access_key_id,
|
|
22
|
+
access_key_secret,
|
|
23
|
+
endpoint,
|
|
24
|
+
bucket_name,
|
|
25
|
+
):
|
|
26
|
+
self.auth = oss2.Auth(access_key_id, access_key_secret)
|
|
27
|
+
self.bucket = oss2.Bucket(self.auth, endpoint, bucket_name)
|
|
28
|
+
|
|
29
|
+
def download_folder(self, source_path, destination_path):
|
|
30
|
+
"""Download a folder from OSS to the local filesystem."""
|
|
31
|
+
if not os.path.exists(destination_path):
|
|
32
|
+
os.makedirs(destination_path)
|
|
33
|
+
|
|
34
|
+
for obj in oss2.ObjectIterator(self.bucket, prefix=source_path):
|
|
35
|
+
local_path = os.path.join(
|
|
36
|
+
destination_path,
|
|
37
|
+
os.path.relpath(obj.key, source_path),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if obj.is_prefix():
|
|
41
|
+
# Create local directory
|
|
42
|
+
os.makedirs(local_path, exist_ok=True)
|
|
43
|
+
else:
|
|
44
|
+
# Download file
|
|
45
|
+
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
|
46
|
+
self.bucket.get_object_to_file(obj.key, local_path)
|
|
47
|
+
|
|
48
|
+
def upload_folder(self, source_path, destination_path):
|
|
49
|
+
"""Upload a local folder to OSS."""
|
|
50
|
+
for root, dirs, files in os.walk(source_path):
|
|
51
|
+
# Upload directory structure
|
|
52
|
+
for d in dirs:
|
|
53
|
+
dir_path = os.path.join(root, d)
|
|
54
|
+
oss_dir_path = os.path.join(
|
|
55
|
+
destination_path,
|
|
56
|
+
os.path.relpath(dir_path, source_path),
|
|
57
|
+
)
|
|
58
|
+
# Maintain structure with an empty object
|
|
59
|
+
self.bucket.put_object(oss_dir_path + "/", b"")
|
|
60
|
+
|
|
61
|
+
# Upload files
|
|
62
|
+
for file in files:
|
|
63
|
+
local_file_path = os.path.join(root, file)
|
|
64
|
+
oss_file_path = os.path.join(
|
|
65
|
+
destination_path,
|
|
66
|
+
os.path.relpath(local_file_path, source_path),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
local_md5 = calculate_md5(local_file_path)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
oss_md5 = (
|
|
73
|
+
self.bucket.head_object(oss_file_path)
|
|
74
|
+
.headers["ETag"]
|
|
75
|
+
.strip('"')
|
|
76
|
+
)
|
|
77
|
+
except oss2.exceptions.NoSuchKey:
|
|
78
|
+
oss_md5 = None
|
|
79
|
+
|
|
80
|
+
# Upload if MD5 does not match or file does not exist
|
|
81
|
+
if local_md5 != oss_md5:
|
|
82
|
+
self.bucket.put_object_from_file(
|
|
83
|
+
oss_file_path,
|
|
84
|
+
local_file_path,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def path_join(self, *args):
|
|
88
|
+
"""Join path components for OSS."""
|
|
89
|
+
return "/".join(args)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import socket
|
|
3
|
+
import subprocess
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.INFO)
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_port_available(port):
|
|
12
|
+
"""
|
|
13
|
+
Check if a given port is available (not in use) on the local system.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
port (int): The port number to check.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
bool: True if the port is available, False if it is in use.
|
|
20
|
+
"""
|
|
21
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
22
|
+
try:
|
|
23
|
+
s.bind(("", port))
|
|
24
|
+
# Port is available
|
|
25
|
+
return True
|
|
26
|
+
except OSError:
|
|
27
|
+
# Port is in use
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def sweep_port(port):
|
|
32
|
+
"""
|
|
33
|
+
Sweep all processes found listening on a given port.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
port (int): The port number.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
int: Number of processes swept (terminated).
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
# Use lsof to find the processes using the port
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
["lsof", "-i", f":{port}"],
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=True,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Parse the output
|
|
51
|
+
lines = result.stdout.strip().split("\n")
|
|
52
|
+
if len(lines) <= 1:
|
|
53
|
+
# No process is using the port
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
# Iterate over each line (excluding the header) and kill each process
|
|
57
|
+
killed_count = 0
|
|
58
|
+
for line in lines[1:]:
|
|
59
|
+
parts = line.split()
|
|
60
|
+
if len(parts) > 1:
|
|
61
|
+
pid = parts[1]
|
|
62
|
+
|
|
63
|
+
# Kill the process using the PID
|
|
64
|
+
subprocess.run(["kill", "-9", pid], check=False)
|
|
65
|
+
killed_count += 1
|
|
66
|
+
|
|
67
|
+
if not is_port_available(port):
|
|
68
|
+
logger.warning(
|
|
69
|
+
f"Port {port} is still in use after killing processes.",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(
|
|
76
|
+
f"An error occurred while killing processes on port {port}: {e}",
|
|
77
|
+
)
|
|
78
|
+
return False
|