agentscope-runtime 0.1.6__py3-none-any.whl → 0.2.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/common/container_clients/__init__.py +0 -0
- agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
- agentscope_runtime/engine/agents/agno_agent.py +8 -10
- agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
- agentscope_runtime/engine/app/__init__.py +6 -0
- agentscope_runtime/engine/app/agent_app.py +239 -0
- agentscope_runtime/engine/app/base_app.py +181 -0
- agentscope_runtime/engine/app/celery_mixin.py +92 -0
- agentscope_runtime/engine/deployers/__init__.py +13 -0
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
- agentscope_runtime/engine/deployers/local_deployer.py +414 -501
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
- agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
- agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
- agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
- agentscope_runtime/engine/runner.py +76 -35
- agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
- agentscope_runtime/engine/schemas/embedding.py +37 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
- agentscope_runtime/engine/schemas/oai_llm.py +538 -0
- agentscope_runtime/engine/schemas/realtime.py +254 -0
- agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
- agentscope_runtime/engine/tracing/__init__.py +9 -3
- agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
- agentscope_runtime/engine/tracing/base.py +66 -34
- agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
- agentscope_runtime/engine/tracing/message_util.py +528 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
- agentscope_runtime/engine/tracing/tracing_util.py +130 -0
- agentscope_runtime/engine/tracing/wrapper.py +794 -169
- agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
- agentscope_runtime/sandbox/client/http_client.py +52 -18
- agentscope_runtime/sandbox/constant.py +3 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
- agentscope_runtime/sandbox/custom/example.py +2 -1
- agentscope_runtime/sandbox/enums.py +0 -1
- agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
- agentscope_runtime/sandbox/model/container.py +6 -0
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
- /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# flake8: noqa: E501
|
|
3
|
+
# flake8: noqa: E541
|
|
4
|
+
# -*- coding: utf-8 -*-
|
|
5
|
+
# pylint: disable=unused-variable, f-string-without-interpolation
|
|
6
|
+
# pylint: disable=line-too-long, too-many-branches
|
|
7
|
+
"""
|
|
8
|
+
Utilities for packaging a local Python project into a distributable wheel
|
|
9
|
+
that can be uploaded and deployed by various deployers.
|
|
10
|
+
|
|
11
|
+
This module extracts and generalizes logic from the legacy test script
|
|
12
|
+
`tests/integrated/test_bailian_fc_deploy/deploy_builder.py` so that
|
|
13
|
+
production deployers can reuse the behaviour in a structured way.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
import uuid
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import List, Tuple
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import tomllib # Python 3.11+
|
|
29
|
+
except ImportError: # pragma: no cover - fallback on older Pythons
|
|
30
|
+
tomllib = None # type: ignore
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def _read_text_file_lines(file_path: Path) -> List[str]:
|
|
34
|
+
if not file_path.is_file():
|
|
35
|
+
return []
|
|
36
|
+
return [
|
|
37
|
+
line.strip()
|
|
38
|
+
for line in file_path.read_text(encoding="utf-8").splitlines()
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def _parse_requirements_txt(req_path: Path) -> List[str]:
|
|
43
|
+
requirements: List[str] = []
|
|
44
|
+
for line in await _read_text_file_lines(req_path):
|
|
45
|
+
if not line or line.startswith("#"):
|
|
46
|
+
continue
|
|
47
|
+
requirements.append(line)
|
|
48
|
+
return requirements
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def _parse_pyproject_toml(pyproject_path: Path) -> List[str]:
|
|
52
|
+
deps: List[str] = []
|
|
53
|
+
if not pyproject_path.is_file():
|
|
54
|
+
return deps
|
|
55
|
+
text = pyproject_path.read_text(encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Prefer stdlib tomllib (Python 3.11+)
|
|
59
|
+
if tomllib is None:
|
|
60
|
+
raise RuntimeError("tomllib not available")
|
|
61
|
+
data = tomllib.loads(text)
|
|
62
|
+
# PEP 621
|
|
63
|
+
proj = data.get("project") or {}
|
|
64
|
+
deps.extend(proj.get("dependencies") or [])
|
|
65
|
+
# Poetry fallback
|
|
66
|
+
poetry = (data.get("tool") or {}).get("poetry") or {}
|
|
67
|
+
poetry_deps = poetry.get("dependencies") or {}
|
|
68
|
+
for name, spec in poetry_deps.items():
|
|
69
|
+
if name.lower() == "python":
|
|
70
|
+
continue
|
|
71
|
+
if isinstance(spec, str):
|
|
72
|
+
deps.append(f"{name}{spec if spec.strip() else ''}")
|
|
73
|
+
elif isinstance(spec, dict):
|
|
74
|
+
version = spec.get("version")
|
|
75
|
+
if version:
|
|
76
|
+
deps.append(f"{name}{version}")
|
|
77
|
+
else:
|
|
78
|
+
deps.append(name)
|
|
79
|
+
except Exception:
|
|
80
|
+
# Minimal non-toml parser fallback: try to extract a dependencies = [ ... ] list
|
|
81
|
+
block_match = re.search(
|
|
82
|
+
r"dependencies\s*=\s*\[(.*?)\]",
|
|
83
|
+
text,
|
|
84
|
+
re.S | re.I,
|
|
85
|
+
)
|
|
86
|
+
if block_match:
|
|
87
|
+
block = block_match.group(1)
|
|
88
|
+
for m in re.finditer(r"['\"]([^'\"]+)['\"]", block):
|
|
89
|
+
deps.append(m.group(1))
|
|
90
|
+
# Poetry fallback: very limited, heuristic
|
|
91
|
+
poetry_block = re.search(
|
|
92
|
+
r"\[tool\.poetry\.dependencies\](.*?)\n\[",
|
|
93
|
+
text,
|
|
94
|
+
re.S,
|
|
95
|
+
)
|
|
96
|
+
if poetry_block:
|
|
97
|
+
for line in poetry_block.group(1).splitlines():
|
|
98
|
+
line = line.strip()
|
|
99
|
+
if not line or line.startswith("#"):
|
|
100
|
+
continue
|
|
101
|
+
if ":" in line:
|
|
102
|
+
# name = "^1.2.3"
|
|
103
|
+
m = re.match(
|
|
104
|
+
r"([A-Za-z0-9_.-]+)\s*=\s*['\"]([^'\"]+)['\"]",
|
|
105
|
+
line,
|
|
106
|
+
)
|
|
107
|
+
if m and m.group(1).lower() != "python":
|
|
108
|
+
deps.append(f"{m.group(1)}{m.group(2)}")
|
|
109
|
+
else:
|
|
110
|
+
# name without version
|
|
111
|
+
name = line.split("#")[0].strip()
|
|
112
|
+
if name and name.lower() != "python":
|
|
113
|
+
deps.append(name)
|
|
114
|
+
return deps
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _gather_user_dependencies(project_dir: Path) -> List[str]:
|
|
118
|
+
pyproject = project_dir / "pyproject.toml"
|
|
119
|
+
req_txt = project_dir / "requirements.txt"
|
|
120
|
+
deps: List[str] = []
|
|
121
|
+
if pyproject.is_file():
|
|
122
|
+
dep = await _parse_pyproject_toml(pyproject)
|
|
123
|
+
deps.extend(dep)
|
|
124
|
+
if req_txt.is_file():
|
|
125
|
+
# Merge requirements.txt too, avoiding duplicates
|
|
126
|
+
existing = set(
|
|
127
|
+
d.split("[", 1)[0]
|
|
128
|
+
.split("=", 1)[0]
|
|
129
|
+
.split("<", 1)[0]
|
|
130
|
+
.split(">", 1)[0]
|
|
131
|
+
.strip()
|
|
132
|
+
.lower()
|
|
133
|
+
for d in deps
|
|
134
|
+
)
|
|
135
|
+
for r in await _parse_requirements_txt(req_txt):
|
|
136
|
+
name_key = (
|
|
137
|
+
r.split("[", 1)[0]
|
|
138
|
+
.split("=", 1)[0]
|
|
139
|
+
.split("<", 1)[0]
|
|
140
|
+
.split(">", 1)[0]
|
|
141
|
+
.strip()
|
|
142
|
+
.lower()
|
|
143
|
+
)
|
|
144
|
+
if name_key not in existing:
|
|
145
|
+
deps.append(r)
|
|
146
|
+
return deps
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _venv_python(venv_dir: Path) -> Path:
|
|
150
|
+
if sys.platform.startswith("win"):
|
|
151
|
+
return venv_dir / "Scripts" / "python.exe"
|
|
152
|
+
return venv_dir / "bin" / "python"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _sanitize_name(name: str) -> str:
|
|
156
|
+
name = re.sub(r"\s+", "_", name)
|
|
157
|
+
name = re.sub(r"[^A-Za-z0-9_\-]", "", name)
|
|
158
|
+
return name.lower()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _write_file(path: Path, content: str) -> None:
|
|
162
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
path.write_text(content, encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def generate_wrapper_project(
|
|
167
|
+
build_root: Path,
|
|
168
|
+
user_project_dir: Path,
|
|
169
|
+
start_cmd: str,
|
|
170
|
+
deploy_name: str,
|
|
171
|
+
telemetry_enabled: bool = True,
|
|
172
|
+
) -> Tuple[Path, Path]:
|
|
173
|
+
"""
|
|
174
|
+
Create a wrapper project under build_root, embedding user project under
|
|
175
|
+
user_bundle/<project_basename>. Returns: (wrapper_project_dir, dist_dir)
|
|
176
|
+
"""
|
|
177
|
+
wrapper_dir = build_root
|
|
178
|
+
|
|
179
|
+
# 1) Copy user project into wrapper under deploy_starter/user_bundle/<project_basename>
|
|
180
|
+
# Put user code inside the deploy_starter package so wheel includes it and preserves project folder name
|
|
181
|
+
project_basename = user_project_dir.name
|
|
182
|
+
bundle_app_dir = (
|
|
183
|
+
wrapper_dir / "deploy_starter" / "user_bundle" / project_basename
|
|
184
|
+
)
|
|
185
|
+
ignore = shutil.ignore_patterns(
|
|
186
|
+
".git",
|
|
187
|
+
".venv",
|
|
188
|
+
".venv_build",
|
|
189
|
+
".agentdev_builds",
|
|
190
|
+
".agentscope_runtime_builds",
|
|
191
|
+
"__pycache__",
|
|
192
|
+
"dist",
|
|
193
|
+
"build",
|
|
194
|
+
"*.pyc",
|
|
195
|
+
".mypy_cache",
|
|
196
|
+
".pytest_cache",
|
|
197
|
+
)
|
|
198
|
+
shutil.copytree(
|
|
199
|
+
user_project_dir,
|
|
200
|
+
bundle_app_dir,
|
|
201
|
+
dirs_exist_ok=True,
|
|
202
|
+
ignore=ignore,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# 2) Dependencies
|
|
206
|
+
user_deps = await _gather_user_dependencies(user_project_dir)
|
|
207
|
+
wrapper_deps = [
|
|
208
|
+
"pyyaml",
|
|
209
|
+
"alibabacloud-oss-v2",
|
|
210
|
+
"alibabacloud-bailian20231229",
|
|
211
|
+
"alibabacloud-credentials",
|
|
212
|
+
"alibabacloud-tea-openapi",
|
|
213
|
+
"alibabacloud-tea-util",
|
|
214
|
+
"python-dotenv",
|
|
215
|
+
]
|
|
216
|
+
# De-duplicate while preserving order
|
|
217
|
+
seen = set()
|
|
218
|
+
install_requires: List[str] = []
|
|
219
|
+
for pkg in wrapper_deps + user_deps:
|
|
220
|
+
key = pkg.strip().lower()
|
|
221
|
+
if key and key not in seen:
|
|
222
|
+
seen.add(key)
|
|
223
|
+
install_requires.append(pkg)
|
|
224
|
+
|
|
225
|
+
# 3) Packaging metadata
|
|
226
|
+
unique_suffix = uuid.uuid4().hex[:8]
|
|
227
|
+
package_name = f"agentdev_starter_{unique_suffix}"
|
|
228
|
+
version = f"0.1.{int(time.time())}"
|
|
229
|
+
|
|
230
|
+
# 4) Template package: deploy_starter
|
|
231
|
+
_write_file(wrapper_dir / "deploy_starter" / "__init__.py", "")
|
|
232
|
+
|
|
233
|
+
main_py = f"""
|
|
234
|
+
import os
|
|
235
|
+
import subprocess
|
|
236
|
+
import sys
|
|
237
|
+
import yaml
|
|
238
|
+
from pathlib import Path
|
|
239
|
+
try:
|
|
240
|
+
from dotenv import load_dotenv # type: ignore
|
|
241
|
+
except Exception:
|
|
242
|
+
load_dotenv = None # type: ignore
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def read_config():
|
|
246
|
+
cfg_path = Path(__file__).with_name('config.yml')
|
|
247
|
+
with cfg_path.open('r', encoding='utf-8') as f:
|
|
248
|
+
return yaml.safe_load(f) or {{}}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main():
|
|
252
|
+
cfg = read_config()
|
|
253
|
+
subdir = cfg.get('APP_SUBDIR_NAME')
|
|
254
|
+
if not subdir:
|
|
255
|
+
print('APP_SUBDIR_NAME missing in config.yml', file=sys.stderr)
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
workdir = Path(__file__).resolve().parent / 'user_bundle' / subdir
|
|
258
|
+
cmd = cfg.get('CMD')
|
|
259
|
+
if not cmd:
|
|
260
|
+
print('CMD missing in config.yml', file=sys.stderr)
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
|
|
263
|
+
if not workdir.is_dir():
|
|
264
|
+
print(f'Workdir not found: {{workdir}}', file=sys.stderr)
|
|
265
|
+
sys.exit(1)
|
|
266
|
+
|
|
267
|
+
cmd_str = str(cmd).strip()
|
|
268
|
+
if cmd_str.startswith('python '):
|
|
269
|
+
cmd_str = f'"{{sys.executable}}" ' + cmd_str[len('python '):]
|
|
270
|
+
elif cmd_str.startswith('python3 '):
|
|
271
|
+
cmd_str = f'"{{sys.executable}}" ' + cmd_str[len('python3 '):]
|
|
272
|
+
elif cmd_str.endswith('.py') and not cmd_str.startswith('"') and ' ' not in cmd_str.split()[0]:
|
|
273
|
+
cmd_str = f'"{{sys.executable}}" ' + cmd_str
|
|
274
|
+
|
|
275
|
+
print(f'[deploy_starter] Starting user service: "{{cmd_str}}" in {{workdir}}')
|
|
276
|
+
|
|
277
|
+
# Load environment variables from user's bundle if present
|
|
278
|
+
if load_dotenv is not None:
|
|
279
|
+
for fname in ('.env', '.env.local'):
|
|
280
|
+
env_file = workdir / fname
|
|
281
|
+
if env_file.is_file():
|
|
282
|
+
try:
|
|
283
|
+
load_dotenv(dotenv_path=env_file, override=False)
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
env = os.environ.copy()
|
|
288
|
+
process = subprocess.Popen(cmd_str, cwd=str(workdir), shell=True, env=env)
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
return_code = process.wait()
|
|
292
|
+
sys.exit(return_code)
|
|
293
|
+
except KeyboardInterrupt:
|
|
294
|
+
try:
|
|
295
|
+
process.terminate()
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
try:
|
|
299
|
+
process.wait(timeout=10)
|
|
300
|
+
except Exception:
|
|
301
|
+
process.kill()
|
|
302
|
+
sys.exit(0)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == '__main__':
|
|
306
|
+
main()
|
|
307
|
+
"""
|
|
308
|
+
_write_file(wrapper_dir / "deploy_starter" / "main.py", main_py)
|
|
309
|
+
|
|
310
|
+
config_yml = f"""
|
|
311
|
+
APP_NAME: "{deploy_name}"
|
|
312
|
+
DEBUG: false
|
|
313
|
+
|
|
314
|
+
HOST: "0.0.0.0"
|
|
315
|
+
PORT: 8080
|
|
316
|
+
RELOAD: true
|
|
317
|
+
|
|
318
|
+
LOG_LEVEL: "INFO"
|
|
319
|
+
|
|
320
|
+
SETUP_PACKAGE_NAME: "{package_name}"
|
|
321
|
+
SETUP_MODULE_NAME: "main"
|
|
322
|
+
SETUP_FUNCTION_NAME: "main"
|
|
323
|
+
SETUP_COMMAND_NAME: "agentdev-starter"
|
|
324
|
+
SETUP_NAME: "agentDev-starter"
|
|
325
|
+
SETUP_VERSION: "{version}"
|
|
326
|
+
SETUP_DESCRIPTION: "agentDev-starter"
|
|
327
|
+
SETUP_LONG_DESCRIPTION: "agentDev-starter services, supporting both direct execution and uvicorn deployment"
|
|
328
|
+
|
|
329
|
+
FC_RUN_CMD: "python3 /code/python/deploy_starter/main.py"
|
|
330
|
+
|
|
331
|
+
TELEMETRY_ENABLE: {'true' if telemetry_enabled else 'false'}
|
|
332
|
+
CMD: "{start_cmd}"
|
|
333
|
+
APP_SUBDIR_NAME: "{project_basename}"
|
|
334
|
+
"""
|
|
335
|
+
_write_file(wrapper_dir / "deploy_starter" / "config.yml", config_yml)
|
|
336
|
+
|
|
337
|
+
setup_py = f"""
|
|
338
|
+
from setuptools import setup, find_packages
|
|
339
|
+
|
|
340
|
+
setup(
|
|
341
|
+
name='{package_name}',
|
|
342
|
+
version='{version}',
|
|
343
|
+
packages=find_packages(),
|
|
344
|
+
include_package_data=True,
|
|
345
|
+
install_requires={install_requires!r},
|
|
346
|
+
)
|
|
347
|
+
"""
|
|
348
|
+
_write_file(wrapper_dir / "setup.py", setup_py)
|
|
349
|
+
|
|
350
|
+
manifest_in = """
|
|
351
|
+
recursive-include deploy_starter *.yml
|
|
352
|
+
recursive-include deploy_starter/user_bundle *
|
|
353
|
+
"""
|
|
354
|
+
_write_file(wrapper_dir / "MANIFEST.in", manifest_in)
|
|
355
|
+
|
|
356
|
+
return wrapper_dir, wrapper_dir / "dist"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
async def build_wheel(project_dir: Path) -> Path:
|
|
360
|
+
"""
|
|
361
|
+
Build a wheel inside an isolated virtual environment to avoid PEP 668
|
|
362
|
+
issues. Returns the path to the built wheel.
|
|
363
|
+
"""
|
|
364
|
+
venv_dir = project_dir / ".venv_build"
|
|
365
|
+
if not venv_dir.exists():
|
|
366
|
+
subprocess.run(
|
|
367
|
+
[sys.executable, "-m", "venv", str(venv_dir)],
|
|
368
|
+
check=True,
|
|
369
|
+
)
|
|
370
|
+
vpy = _venv_python(venv_dir)
|
|
371
|
+
subprocess.run(
|
|
372
|
+
[str(vpy), "-m", "pip", "install", "--upgrade", "pip", "build"],
|
|
373
|
+
check=True,
|
|
374
|
+
)
|
|
375
|
+
subprocess.run([str(vpy), "-m", "build"], cwd=str(project_dir), check=True)
|
|
376
|
+
dist_dir = project_dir / "dist"
|
|
377
|
+
whls = sorted(
|
|
378
|
+
dist_dir.glob("*.whl"),
|
|
379
|
+
key=lambda p: p.stat().st_mtime,
|
|
380
|
+
reverse=True,
|
|
381
|
+
)
|
|
382
|
+
if not whls:
|
|
383
|
+
raise RuntimeError("Wheel build failed: no .whl produced")
|
|
384
|
+
return whls[0]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def default_deploy_name() -> str:
|
|
388
|
+
ts = time.strftime("%Y%m%d%H%M%S", time.localtime())
|
|
389
|
+
return f"deploy-{ts}-{uuid.uuid4().hex[:6]}"
|