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.
Files changed (131) hide show
  1. agentscope_runtime/__init__.py +4 -0
  2. agentscope_runtime/engine/__init__.py +9 -0
  3. agentscope_runtime/engine/agents/__init__.py +2 -0
  4. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
  5. agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
  6. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
  7. agentscope_runtime/engine/agents/agno_agent.py +220 -0
  8. agentscope_runtime/engine/agents/base_agent.py +29 -0
  9. agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
  10. agentscope_runtime/engine/agents/llm_agent.py +51 -0
  11. agentscope_runtime/engine/deployers/__init__.py +3 -0
  12. agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
  17. agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
  18. agentscope_runtime/engine/deployers/base.py +17 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +586 -0
  20. agentscope_runtime/engine/helpers/helper.py +127 -0
  21. agentscope_runtime/engine/llms/__init__.py +3 -0
  22. agentscope_runtime/engine/llms/base_llm.py +60 -0
  23. agentscope_runtime/engine/llms/qwen_llm.py +47 -0
  24. agentscope_runtime/engine/misc/__init__.py +0 -0
  25. agentscope_runtime/engine/runner.py +186 -0
  26. agentscope_runtime/engine/schemas/__init__.py +0 -0
  27. agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
  28. agentscope_runtime/engine/schemas/context.py +54 -0
  29. agentscope_runtime/engine/services/__init__.py +9 -0
  30. agentscope_runtime/engine/services/base.py +77 -0
  31. agentscope_runtime/engine/services/context_manager.py +129 -0
  32. agentscope_runtime/engine/services/environment_manager.py +50 -0
  33. agentscope_runtime/engine/services/manager.py +174 -0
  34. agentscope_runtime/engine/services/memory_service.py +270 -0
  35. agentscope_runtime/engine/services/sandbox_service.py +198 -0
  36. agentscope_runtime/engine/services/session_history_service.py +256 -0
  37. agentscope_runtime/engine/tracing/__init__.py +40 -0
  38. agentscope_runtime/engine/tracing/base.py +309 -0
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
  40. agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
  41. agentscope_runtime/engine/tracing/wrapper.py +321 -0
  42. agentscope_runtime/sandbox/__init__.py +14 -0
  43. agentscope_runtime/sandbox/box/__init__.py +0 -0
  44. agentscope_runtime/sandbox/box/base/__init__.py +0 -0
  45. agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
  46. agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
  47. agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
  48. agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
  53. agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
  54. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
  55. agentscope_runtime/sandbox/box/sandbox.py +115 -0
  56. agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
  57. agentscope_runtime/sandbox/box/shared/app.py +44 -0
  58. agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
  59. agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
  60. agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
  61. agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
  62. agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
  64. agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
  65. agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
  66. agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
  67. agentscope_runtime/sandbox/box/training_box/base.py +120 -0
  68. agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
  69. agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
  70. agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
  71. agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
  72. agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
  73. agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
  74. agentscope_runtime/sandbox/build.py +213 -0
  75. agentscope_runtime/sandbox/client/__init__.py +5 -0
  76. agentscope_runtime/sandbox/client/http_client.py +527 -0
  77. agentscope_runtime/sandbox/client/training_client.py +265 -0
  78. agentscope_runtime/sandbox/constant.py +5 -0
  79. agentscope_runtime/sandbox/custom/__init__.py +16 -0
  80. agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
  81. agentscope_runtime/sandbox/custom/example.py +37 -0
  82. agentscope_runtime/sandbox/enums.py +68 -0
  83. agentscope_runtime/sandbox/manager/__init__.py +4 -0
  84. agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
  85. agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
  86. agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
  87. agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
  89. agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
  90. agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
  91. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
  92. agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
  93. agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
  94. agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
  95. agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
  96. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
  97. agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
  98. agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
  99. agentscope_runtime/sandbox/manager/server/app.py +194 -0
  100. agentscope_runtime/sandbox/manager/server/config.py +68 -0
  101. agentscope_runtime/sandbox/manager/server/models.py +17 -0
  102. agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
  103. agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
  104. agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
  105. agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
  106. agentscope_runtime/sandbox/manager/utils.py +78 -0
  107. agentscope_runtime/sandbox/mcp_server.py +192 -0
  108. agentscope_runtime/sandbox/model/__init__.py +12 -0
  109. agentscope_runtime/sandbox/model/api.py +16 -0
  110. agentscope_runtime/sandbox/model/container.py +72 -0
  111. agentscope_runtime/sandbox/model/manager_config.py +158 -0
  112. agentscope_runtime/sandbox/registry.py +129 -0
  113. agentscope_runtime/sandbox/tools/__init__.py +12 -0
  114. agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
  115. agentscope_runtime/sandbox/tools/base/tool.py +52 -0
  116. agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
  117. agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
  118. agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
  119. agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
  120. agentscope_runtime/sandbox/tools/function_tool.py +321 -0
  121. agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
  122. agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
  123. agentscope_runtime/sandbox/tools/tool.py +123 -0
  124. agentscope_runtime/sandbox/tools/utils.py +68 -0
  125. agentscope_runtime/version.py +2 -0
  126. agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
  127. agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
  128. agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
  129. agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
  130. agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
  131. 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,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .data_storage import DataStorage
3
+ from .local_storage import LocalStorage
4
+ from .oss_storage import OSSStorage
5
+
6
+ __all__ = [
7
+ "DataStorage",
8
+ "LocalStorage",
9
+ "OSSStorage",
10
+ ]
@@ -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