aishipbox 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.
- aishipbox/__init__.py +3 -0
- aishipbox/__main__.py +6 -0
- aishipbox/algo/__init__.py +58 -0
- aishipbox/algo/commands/__init__.py +0 -0
- aishipbox/algo/commands/debug.py +40 -0
- aishipbox/algo/commands/install_deps.py +47 -0
- aishipbox/algo/commands/new.py +96 -0
- aishipbox/algo/commands/pack.py +32 -0
- aishipbox/algo/commands/run.py +42 -0
- aishipbox/algo/commands/stubs.py +31 -0
- aishipbox/algo/runner.py +126 -0
- aishipbox/algo/stubs/__init__.py +1 -0
- aishipbox/algo/stubs/pyproject.toml +12 -0
- aishipbox/algo/stubs/rest/__init__.py +1 -0
- aishipbox/algo/stubs/rest/process_base.py +6 -0
- aishipbox/algo/templates/AGENTS.md.tmpl +22 -0
- aishipbox/algo/templates/__init__.py +1 -0
- aishipbox/algo/templates/basic/.env.example +5 -0
- aishipbox/algo/templates/basic/__init__.py +1 -0
- aishipbox/algo/templates/basic/main.py +27 -0
- aishipbox/algo/templates/basic/requirements.txt +13 -0
- aishipbox/algo/templates/basic/utils.py +6 -0
- aishipbox/algo/templates/cv/.env.example +5 -0
- aishipbox/algo/templates/cv/__init__.py +1 -0
- aishipbox/algo/templates/cv/main.py +86 -0
- aishipbox/algo/templates/cv/requirements.txt +13 -0
- aishipbox/algo/templates/cv/utils.py +23 -0
- aishipbox/algo/templates/predict/.env.example +5 -0
- aishipbox/algo/templates/predict/__init__.py +1 -0
- aishipbox/algo/templates/predict/main.py +88 -0
- aishipbox/algo/templates/predict/requirements.txt +13 -0
- aishipbox/algo/templates/predict/utils.py +13 -0
- aishipbox/cli.py +52 -0
- aishipbox/core/__init__.py +0 -0
- aishipbox/core/config.py +84 -0
- aishipbox/core/env.py +25 -0
- aishipbox/core/packaging.py +52 -0
- aishipbox/core/strings.py +47 -0
- aishipbox/core/ui.py +69 -0
- aishipbox/core/venv.py +61 -0
- aishipbox/op/__init__.py +117 -0
- aishipbox/op/commands/__init__.py +0 -0
- aishipbox/op/commands/debug.py +28 -0
- aishipbox/op/commands/dep.py +141 -0
- aishipbox/op/commands/new.py +143 -0
- aishipbox/op/commands/pack.py +61 -0
- aishipbox/op/commands/run.py +86 -0
- aishipbox/op/ma_utils_mock/__init__.py +0 -0
- aishipbox/op/ma_utils_mock/ma_utils/__init__.py +27 -0
- aishipbox/op/manifest.py +284 -0
- aishipbox/op/moxing_mock/__init__.py +0 -0
- aishipbox/op/moxing_mock/moxing/__init__.py +1 -0
- aishipbox/op/moxing_mock/moxing/file.py +171 -0
- aishipbox/op/runner.py +231 -0
- aishipbox/op/templates/AGENTS.md.tmpl +281 -0
- aishipbox/op/templates/__init__.py +0 -0
- aishipbox/op/templates/env.example.tmpl +6 -0
- aishipbox/op/templates/gitignore.tmpl +8 -0
- aishipbox/op/templates/install.sh.example.tmpl +14 -0
- aishipbox/op/templates/process.blank.py.tmpl +47 -0
- aishipbox/op/templates/process.transform.py.tmpl +64 -0
- aishipbox/op/templates/requirements.txt.tmpl +16 -0
- aishipbox/op/wizard.py +32 -0
- aishipbox-0.1.0.dist-info/METADATA +171 -0
- aishipbox-0.1.0.dist-info/RECORD +67 -0
- aishipbox-0.1.0.dist-info/WHEEL +4 -0
- aishipbox-0.1.0.dist-info/entry_points.txt +2 -0
aishipbox/__init__.py
ADDED
aishipbox/__main__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""algo subcommand group dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def dispatch(argv: List[str]) -> int:
|
|
10
|
+
parser = argparse.ArgumentParser(prog="aishipbox algo", description="算法包服务项目管理")
|
|
11
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
12
|
+
|
|
13
|
+
p_new = sub.add_parser("new", help="新建算法服务项目")
|
|
14
|
+
p_new.add_argument("name")
|
|
15
|
+
p_new.add_argument("-d", "--dir", default=".")
|
|
16
|
+
p_new.add_argument("-t", "--template", choices=["basic", "predict", "cv"])
|
|
17
|
+
p_new.add_argument("--yes", action="store_true")
|
|
18
|
+
|
|
19
|
+
p_run = sub.add_parser("run", help="本地运行算法服务")
|
|
20
|
+
p_run.add_argument("path", nargs="?", default=".")
|
|
21
|
+
p_run.add_argument("-p", "--port", type=int, default=8080)
|
|
22
|
+
p_run.add_argument("--host", default="127.0.0.1")
|
|
23
|
+
p_run.add_argument("--debug", action="store_true")
|
|
24
|
+
p_run.add_argument("--debug-port", type=int, default=5678)
|
|
25
|
+
|
|
26
|
+
p_pack = sub.add_parser("pack", help="打包算法服务")
|
|
27
|
+
p_pack.add_argument("path", nargs="?", default=".")
|
|
28
|
+
p_pack.add_argument("-o", "--output")
|
|
29
|
+
|
|
30
|
+
p_debug = sub.add_parser("debug", help="生成 VS Code 调试配置")
|
|
31
|
+
p_debug.add_argument("path", nargs="?", default=".")
|
|
32
|
+
|
|
33
|
+
p_stubs = sub.add_parser("stubs", help="安装 IDE 类型补全包")
|
|
34
|
+
p_stubs.add_argument("path", nargs="?", default=".")
|
|
35
|
+
|
|
36
|
+
p_deps = sub.add_parser("install-deps", help="安装托管环境常用依赖")
|
|
37
|
+
p_deps.add_argument("path", nargs="?", default=".")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
args = parser.parse_args(argv)
|
|
41
|
+
except SystemExit as e:
|
|
42
|
+
return int(e.code) if e.code is not None else 2
|
|
43
|
+
|
|
44
|
+
from aishipbox.algo.commands import new, run, pack, debug, stubs, install_deps
|
|
45
|
+
|
|
46
|
+
if args.cmd == "new":
|
|
47
|
+
return new.execute(args.name, args.dir, args.template, args.yes)
|
|
48
|
+
if args.cmd == "run":
|
|
49
|
+
return run.execute(args.path, args.host, args.port, args.debug, args.debug_port)
|
|
50
|
+
if args.cmd == "pack":
|
|
51
|
+
return pack.execute(args.path, args.output)
|
|
52
|
+
if args.cmd == "debug":
|
|
53
|
+
return debug.execute(args.path)
|
|
54
|
+
if args.cmd == "stubs":
|
|
55
|
+
return stubs.execute(args.path)
|
|
56
|
+
if args.cmd == "install-deps":
|
|
57
|
+
return install_deps.execute(args.path)
|
|
58
|
+
return 2
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Generate VS Code debug configuration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
LAUNCH_CONFIG = {
|
|
7
|
+
"version": "0.2.0",
|
|
8
|
+
"configurations": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Attach to Algo Service",
|
|
11
|
+
"type": "debugpy",
|
|
12
|
+
"request": "attach",
|
|
13
|
+
"connect": {
|
|
14
|
+
"host": "127.0.0.1",
|
|
15
|
+
"port": 5678
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def execute(path: str) -> int:
|
|
23
|
+
project_dir = Path(path).resolve()
|
|
24
|
+
vscode_dir = project_dir / ".vscode"
|
|
25
|
+
launch_file = vscode_dir / "launch.json"
|
|
26
|
+
|
|
27
|
+
vscode_dir.mkdir(exist_ok=True)
|
|
28
|
+
|
|
29
|
+
if launch_file.exists():
|
|
30
|
+
print(f"警告:{launch_file} 已存在,将覆盖")
|
|
31
|
+
|
|
32
|
+
launch_file.write_text(json.dumps(LAUNCH_CONFIG, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
33
|
+
print(f"已生成:{launch_file}")
|
|
34
|
+
|
|
35
|
+
print("\n调试步骤:")
|
|
36
|
+
print(" 1. 在代码中设置断点")
|
|
37
|
+
print(" 2. 运行:aishipbox algo run --debug")
|
|
38
|
+
print(" 3. 在 VS Code 中按 F5 附加调试器")
|
|
39
|
+
|
|
40
|
+
return 0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Install hosting-environment dependencies into the project venv."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from aishipbox.core.venv import python_executable, require_uv, VenvError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
HOSTING_DEPS = [
|
|
12
|
+
"numpy",
|
|
13
|
+
"pandas",
|
|
14
|
+
"scipy",
|
|
15
|
+
"scikit-learn",
|
|
16
|
+
"requests",
|
|
17
|
+
"flask",
|
|
18
|
+
"opencv-python",
|
|
19
|
+
"pillow",
|
|
20
|
+
"pyyaml",
|
|
21
|
+
"pydantic",
|
|
22
|
+
"aiohttp",
|
|
23
|
+
"openpyxl",
|
|
24
|
+
"lxml",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def execute(path: str = ".") -> int:
|
|
29
|
+
project_dir = Path(path).resolve()
|
|
30
|
+
try:
|
|
31
|
+
py = python_executable(project_dir)
|
|
32
|
+
uv = require_uv()
|
|
33
|
+
except VenvError as e:
|
|
34
|
+
print(str(e))
|
|
35
|
+
return 1
|
|
36
|
+
|
|
37
|
+
print("安装托管环境常用依赖...")
|
|
38
|
+
print(f" 包:{', '.join(HOSTING_DEPS)}\n")
|
|
39
|
+
|
|
40
|
+
cmd = [uv, "pip", "install", "--python", str(py)] + HOSTING_DEPS
|
|
41
|
+
result = subprocess.run(cmd, text=True)
|
|
42
|
+
if result.returncode != 0:
|
|
43
|
+
print("\n错误:部分依赖安装失败")
|
|
44
|
+
return 1
|
|
45
|
+
|
|
46
|
+
print("\n完成:托管环境依赖已安装到项目 .venv/。")
|
|
47
|
+
return 0
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""algo new: create a new algorithm service project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import resources
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from string import Template
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from aishipbox import __version__
|
|
11
|
+
from aishipbox.core import strings
|
|
12
|
+
from aishipbox.core.config import HOSTED_RUNTIMES, ProjectConfig, write_project_config
|
|
13
|
+
from aishipbox.core.ui import stdin_is_interactive
|
|
14
|
+
from aishipbox.core.venv import provision_venv, pip_install
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
TEMPLATES = ("basic", "predict", "cv")
|
|
18
|
+
TEMPLATE_DEPS = {
|
|
19
|
+
"basic": ["requests"],
|
|
20
|
+
"predict": ["pandas", "requests"],
|
|
21
|
+
"cv": ["opencv-python", "numpy", "requests"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def execute(name: str, parent_dir: str, template: Optional[str] = None, yes: bool = False) -> int:
|
|
26
|
+
if "-" in name:
|
|
27
|
+
print(f"项目名不允许包含连字符:{name},请改用下划线 {name.replace('-', '_')}")
|
|
28
|
+
return 1
|
|
29
|
+
|
|
30
|
+
parent = Path(parent_dir).resolve()
|
|
31
|
+
project_dir = parent / name
|
|
32
|
+
if project_dir.exists():
|
|
33
|
+
print(strings.TARGET_DIR_EXISTS.format(path=project_dir))
|
|
34
|
+
return 1
|
|
35
|
+
|
|
36
|
+
if template is None:
|
|
37
|
+
if yes:
|
|
38
|
+
template = "basic"
|
|
39
|
+
elif not stdin_is_interactive():
|
|
40
|
+
print(strings.NON_INTERACTIVE_NO_WIZARD.format(
|
|
41
|
+
example=f"aishipbox algo new {name} --yes -t basic"
|
|
42
|
+
))
|
|
43
|
+
return 2
|
|
44
|
+
else:
|
|
45
|
+
template = _prompt_template()
|
|
46
|
+
if template not in TEMPLATES:
|
|
47
|
+
print(f"未知模板:{template}")
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
project_dir.mkdir(parents=True)
|
|
51
|
+
|
|
52
|
+
template_pkg = resources.files(f"aishipbox.algo.templates.{template}")
|
|
53
|
+
for item in template_pkg.iterdir():
|
|
54
|
+
if item.is_file() and not item.name.startswith("__"):
|
|
55
|
+
content = item.read_text(encoding="utf-8").replace("${name}", name)
|
|
56
|
+
(project_dir / item.name).write_text(content, encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
agents_tmpl = resources.files("aishipbox.algo.templates").joinpath("AGENTS.md.tmpl").read_text(encoding="utf-8")
|
|
59
|
+
(project_dir / "AGENTS.md").write_text(
|
|
60
|
+
Template(agents_tmpl).safe_substitute(aishipbox_version=__version__),
|
|
61
|
+
encoding="utf-8",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
write_project_config(project_dir, ProjectConfig(type="algo", runtime=HOSTED_RUNTIMES["algo"]))
|
|
65
|
+
|
|
66
|
+
_provision_and_install(project_dir, template)
|
|
67
|
+
|
|
68
|
+
print(f"\n项目已创建:{project_dir}")
|
|
69
|
+
print(f"\n{strings.NEXT_STEPS_HEADER}")
|
|
70
|
+
print(f" 1. cd {name}")
|
|
71
|
+
print(f" 2. cp .env.example .env # 如存在")
|
|
72
|
+
print(f" 3. 编辑 main.py 实现算法")
|
|
73
|
+
print(f" 4. aishipbox algo run")
|
|
74
|
+
print(f" 5. aishipbox algo pack")
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _prompt_template() -> str:
|
|
79
|
+
print(strings.ALGO_SELECT_TEMPLATE)
|
|
80
|
+
print(f" 1. {strings.ALGO_TEMPLATE_BASIC}")
|
|
81
|
+
print(f" 2. {strings.ALGO_TEMPLATE_PREDICT}")
|
|
82
|
+
print(f" 3. {strings.ALGO_TEMPLATE_CV}")
|
|
83
|
+
while True:
|
|
84
|
+
choice = input("请输入编号或名称 [1]:").strip() or "1"
|
|
85
|
+
mapping = {"1": "basic", "2": "predict", "3": "cv",
|
|
86
|
+
"basic": "basic", "predict": "predict", "cv": "cv"}
|
|
87
|
+
if choice in mapping:
|
|
88
|
+
return mapping[choice]
|
|
89
|
+
print(f"无效选择:{choice}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _provision_and_install(project_dir: Path, template: str) -> None:
|
|
93
|
+
provision_venv(project_dir, HOSTED_RUNTIMES["algo"])
|
|
94
|
+
deps = TEMPLATE_DEPS.get(template, [])
|
|
95
|
+
if deps:
|
|
96
|
+
pip_install(project_dir, *deps)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""algo pack: build a .tar.gz of the service for deployment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from aishipbox.core.packaging import DEFAULT_EXCLUDES, build_tar
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ALGO_EXCLUDES = DEFAULT_EXCLUDES | {
|
|
12
|
+
".aishipbox.toml",
|
|
13
|
+
"AGENTS.md",
|
|
14
|
+
".env.example",
|
|
15
|
+
"server.py",
|
|
16
|
+
"run.py",
|
|
17
|
+
"pack.py",
|
|
18
|
+
"test_client.py",
|
|
19
|
+
"rest",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def execute(path: str, output: Optional[str] = None) -> int:
|
|
24
|
+
service_dir = Path(path).resolve()
|
|
25
|
+
if not (service_dir / "main.py").exists():
|
|
26
|
+
print(f"main.py 不存在:{service_dir}")
|
|
27
|
+
return 1
|
|
28
|
+
|
|
29
|
+
out_path = Path(output) if output else Path(f"{service_dir.name}.tar.gz")
|
|
30
|
+
build_tar(service_dir, out_path, excludes=ALGO_EXCLUDES, gzip=True)
|
|
31
|
+
print(f"已生成:{out_path}")
|
|
32
|
+
return 0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""algo run: launch the local HTTP server using the project's venv."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from aishipbox.core.env import load_env_file
|
|
10
|
+
from aishipbox.core.venv import python_executable, VenvError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def execute(path: str, host: str, port: int, debug: bool, debug_port: int) -> int:
|
|
14
|
+
service_dir = Path(path).resolve()
|
|
15
|
+
main_file = service_dir / "main.py"
|
|
16
|
+
if not main_file.exists():
|
|
17
|
+
print(f"main.py 不存在:{service_dir}")
|
|
18
|
+
return 1
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
py = python_executable(service_dir)
|
|
22
|
+
except VenvError as e:
|
|
23
|
+
print(str(e))
|
|
24
|
+
return 1
|
|
25
|
+
|
|
26
|
+
env = os.environ.copy()
|
|
27
|
+
env.update(load_env_file(service_dir / ".env"))
|
|
28
|
+
env.update({
|
|
29
|
+
"ALGO_SERVICE_DIR": str(service_dir),
|
|
30
|
+
"ALGO_HOST": host,
|
|
31
|
+
"ALGO_PORT": str(port),
|
|
32
|
+
"ALGO_DEBUG": "1" if debug else "0",
|
|
33
|
+
"ALGO_DEBUG_PORT": str(debug_port),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
runner_script = Path(__file__).resolve().parent.parent / "runner.py"
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
result = subprocess.run([str(py), str(runner_script)], env=env, cwd=str(service_dir.parent))
|
|
40
|
+
return result.returncode
|
|
41
|
+
except KeyboardInterrupt:
|
|
42
|
+
return 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Install stub packages for IDE/linting support into the project venv."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from aishipbox.core.venv import python_executable, require_uv, VenvError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def execute(path: str = ".") -> int:
|
|
13
|
+
project_dir = Path(path).resolve()
|
|
14
|
+
try:
|
|
15
|
+
py = python_executable(project_dir)
|
|
16
|
+
uv = require_uv()
|
|
17
|
+
except VenvError as e:
|
|
18
|
+
print(str(e))
|
|
19
|
+
return 1
|
|
20
|
+
|
|
21
|
+
stubs_dir = resources.files("aishipbox.algo.stubs")
|
|
22
|
+
with resources.as_file(stubs_dir) as stubs_path:
|
|
23
|
+
print(f"安装 rest-stubs:{stubs_path}")
|
|
24
|
+
cmd = [uv, "pip", "install", "--python", str(py), str(stubs_path)]
|
|
25
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
26
|
+
if result.returncode != 0:
|
|
27
|
+
print(f"错误:{result.stderr}")
|
|
28
|
+
return 1
|
|
29
|
+
print("已安装 rest-stubs")
|
|
30
|
+
print("\n'rest.process_base' 现在可以被解析(IDE 类型提示)。")
|
|
31
|
+
return 0
|
aishipbox/algo/runner.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Runner script executed in the user's virtual environment."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
logging.basicConfig(
|
|
11
|
+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
12
|
+
)
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PROCESS_BASE_STUB = '''
|
|
17
|
+
class ProcessBase:
|
|
18
|
+
def __init__(self, url: str = ""):
|
|
19
|
+
self.url = url
|
|
20
|
+
'''
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_handler(processor):
|
|
24
|
+
"""Create HTTP handler with processor instance."""
|
|
25
|
+
|
|
26
|
+
class AlgoHandler(BaseHTTPRequestHandler):
|
|
27
|
+
def do_POST(self):
|
|
28
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
29
|
+
body = self.rfile.read(content_length)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
params = json.loads(body)
|
|
33
|
+
result, status_code = processor.process(params)
|
|
34
|
+
|
|
35
|
+
self.send_response(status_code)
|
|
36
|
+
self.send_header("Content-Type", "application/json")
|
|
37
|
+
self.end_headers()
|
|
38
|
+
self.wfile.write(json.dumps(result, ensure_ascii=False).encode("utf-8"))
|
|
39
|
+
except json.JSONDecodeError:
|
|
40
|
+
self.send_error(400, "Invalid JSON")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.exception("Error processing request")
|
|
43
|
+
self.send_error(500, str(e))
|
|
44
|
+
|
|
45
|
+
def do_GET(self):
|
|
46
|
+
if self.path == "/health":
|
|
47
|
+
self.send_response(200)
|
|
48
|
+
self.send_header("Content-Type", "application/json")
|
|
49
|
+
self.end_headers()
|
|
50
|
+
self.wfile.write(b'{"status": "ok"}')
|
|
51
|
+
else:
|
|
52
|
+
self.send_error(404, "Not Found")
|
|
53
|
+
|
|
54
|
+
return AlgoHandler
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main():
|
|
58
|
+
# Read configuration from environment
|
|
59
|
+
service_dir = Path(os.environ["ALGO_SERVICE_DIR"]).resolve()
|
|
60
|
+
host = os.environ["ALGO_HOST"]
|
|
61
|
+
port = int(os.environ["ALGO_PORT"])
|
|
62
|
+
debug = os.environ.get("ALGO_DEBUG") == "1"
|
|
63
|
+
debug_port = int(os.environ.get("ALGO_DEBUG_PORT", "5678"))
|
|
64
|
+
|
|
65
|
+
service_name = service_dir.name
|
|
66
|
+
|
|
67
|
+
if debug:
|
|
68
|
+
try:
|
|
69
|
+
import debugpy
|
|
70
|
+
debugpy.listen(("0.0.0.0", debug_port))
|
|
71
|
+
logger.info(f"Debugger listening on port {debug_port}")
|
|
72
|
+
logger.info("Waiting for VS Code to attach (press F5)...")
|
|
73
|
+
debugpy.wait_for_client()
|
|
74
|
+
logger.info("Debugger attached")
|
|
75
|
+
except ImportError:
|
|
76
|
+
logger.error("debugpy not installed. Install it with: pip install debugpy")
|
|
77
|
+
return 1
|
|
78
|
+
|
|
79
|
+
# Inject ProcessBase stub into rest.process_base
|
|
80
|
+
import types
|
|
81
|
+
rest_module = types.ModuleType("rest")
|
|
82
|
+
process_base_module = types.ModuleType("rest.process_base")
|
|
83
|
+
exec(PROCESS_BASE_STUB, process_base_module.__dict__)
|
|
84
|
+
rest_module.process_base = process_base_module
|
|
85
|
+
sys.modules["rest"] = rest_module
|
|
86
|
+
sys.modules["rest.process_base"] = process_base_module
|
|
87
|
+
|
|
88
|
+
# Add parent directory to path for absolute imports
|
|
89
|
+
sys.path.insert(0, str(service_dir.parent))
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Import as package to match production environment
|
|
93
|
+
import importlib
|
|
94
|
+
main_module = importlib.import_module(f"{service_name}.main")
|
|
95
|
+
AlgoProcessor = main_module.AlgoProcessor
|
|
96
|
+
except ImportError as e:
|
|
97
|
+
logger.error(f"Error importing AlgoProcessor: {e}")
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
processor = AlgoProcessor()
|
|
101
|
+
handler = create_handler(processor)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
server = HTTPServer((host, port), handler)
|
|
105
|
+
except OSError as e:
|
|
106
|
+
if e.errno == 48: # Address already in use
|
|
107
|
+
logger.error(f"Port {port} is already in use.")
|
|
108
|
+
logger.error(f"Try: algo run --port {port + 1}")
|
|
109
|
+
return 1
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
logger.info(f"Starting server at http://{host}:{port}")
|
|
113
|
+
logger.info(f"POST http://{host}:{port}/ - Process request")
|
|
114
|
+
logger.info(f"GET http://{host}:{port}/health - Health check")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
server.serve_forever()
|
|
118
|
+
except KeyboardInterrupt:
|
|
119
|
+
logger.info("Shutting down")
|
|
120
|
+
server.shutdown()
|
|
121
|
+
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Stub packages for local development."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rest-stubs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Stub package for hosting platform rest module"
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
|
|
7
|
+
[build-system]
|
|
8
|
+
requires = ["hatchling"]
|
|
9
|
+
build-backend = "hatchling.build"
|
|
10
|
+
|
|
11
|
+
[tool.hatch.build.targets.wheel]
|
|
12
|
+
packages = ["rest"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Stub package for rest module (provided by hosting platform at runtime)."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
本项目类型:算法包服务(algo)
|
|
4
|
+
托管运行时:Python 3.9
|
|
5
|
+
工具:aishipbox v${aishipbox_version}
|
|
6
|
+
|
|
7
|
+
## 入口
|
|
8
|
+
- main.py 中的 `AlgoProcessor` 类
|
|
9
|
+
|
|
10
|
+
## 常用命令
|
|
11
|
+
- 本地运行: aishipbox algo run
|
|
12
|
+
- 启动调试: aishipbox algo run --debug (需配合 VS Code "Attach to Algo Service")
|
|
13
|
+
- 打包: aishipbox algo pack
|
|
14
|
+
- 安装托管依赖:aishipbox algo install-deps
|
|
15
|
+
|
|
16
|
+
## 关键约束
|
|
17
|
+
- 部署到 MAS 托管服务,结构遵循 main.py + requirements.txt + dependency/。
|
|
18
|
+
- requirements.txt 中可引用 dependency/ 下的离线 wheel。
|
|
19
|
+
- 不要修改 .aishipbox.toml 的 schema_version 与 type 字段。
|
|
20
|
+
|
|
21
|
+
## 平台参考
|
|
22
|
+
- 算法手册:参见 MAS 文档
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""algo-cli templates."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""${name} algorithm service."""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""${name} - Algorithm Service Entry Point."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from rest.process_base import ProcessBase
|
|
10
|
+
from .utils import example_function
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(
|
|
13
|
+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
14
|
+
)
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AlgoProcessor(ProcessBase):
|
|
19
|
+
"""Algorithm Processor - maintains API compatibility with hosting platform."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__(url="")
|
|
23
|
+
|
|
24
|
+
def process(self, params_dict: dict) -> tuple[dict, int]:
|
|
25
|
+
value = params_dict.get("value", "default")
|
|
26
|
+
result = example_function(value)
|
|
27
|
+
return {"result": result}, 200
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 在此添加基础镜像中未包含的依赖
|
|
2
|
+
# 将 .whl 文件放入 dependency/ 文件夹,并按以下格式引用:
|
|
3
|
+
# dependency/your-package-1.0.0-py3-none-any.whl
|
|
4
|
+
#
|
|
5
|
+
# 基础镜像已安装的包(请勿重复添加):
|
|
6
|
+
# 核心库: numpy==1.26.4, pandas==2.1.2, scipy==1.11.2
|
|
7
|
+
# 机器学习: scikit-learn==1.0.2, xgboost==1.6.2, lightgbm==3.3.5
|
|
8
|
+
# 深度学习: tensorflow==2.16.1, keras==3.9.0, torch==1.13.1
|
|
9
|
+
# 图像处理: opencv-python==4.7.0.72, pillow==11.2.1
|
|
10
|
+
# 数据处理: pyarrow==20.0.0, openpyxl==3.1.2, lxml==5.4.0
|
|
11
|
+
# 网络框架: flask==3.0.0, requests==2.32.0, aiohttp==3.12.11, gunicorn==22.0.0
|
|
12
|
+
# 分布式: ray==2.46.0
|
|
13
|
+
# 工具库: pyyaml==6.0.2, pydantic==1.10.21, cryptography==45.0.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""${name} algorithm service."""
|