agk 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.
- agk/__init__.py +23 -0
- agk/cli.py +189 -0
- agk/core.py +490 -0
- agk/events.py +85 -0
- agk/gates.py +66 -0
- agk/templates/julia/Project.toml.tmpl +6 -0
- agk/templates/julia/README.md.tmpl +28 -0
- agk/templates/julia/app.jl.tmpl +30 -0
- agk/templates/julia/index.html.tmpl +13 -0
- agk/templates/python/README.md.tmpl +32 -0
- agk/templates/python/app.py.tmpl +67 -0
- agk/templates/python/index.html.tmpl +13 -0
- agk/templates/python/pyproject.toml.tmpl +9 -0
- agk-0.1.0.dist-info/METADATA +74 -0
- agk-0.1.0.dist-info/RECORD +19 -0
- agk-0.1.0.dist-info/WHEEL +5 -0
- agk-0.1.0.dist-info/entry_points.txt +2 -0
- agk-0.1.0.dist-info/licenses/LICENSE +202 -0
- agk-0.1.0.dist-info/top_level.txt +1 -0
agk/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""app-gui-kit Python サーバ SDK。
|
|
2
|
+
|
|
3
|
+
シミュレーションエージェント系アプリの Flask 定型(SSE ストリーミング、
|
|
4
|
+
単一フライト、ライフサイクル watchdog、成果物配信、remote-job キュー、
|
|
5
|
+
アップロード、HITL ゲート、起動処理)を提供する。
|
|
6
|
+
|
|
7
|
+
使い方::
|
|
8
|
+
|
|
9
|
+
from agk import AgkApp
|
|
10
|
+
|
|
11
|
+
def runner(req, ctx):
|
|
12
|
+
ctx.emit.text("hello")
|
|
13
|
+
ctx.emit.done(summary="finished")
|
|
14
|
+
|
|
15
|
+
AgkApp("my-app", runner).run()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .core import AgkApp, AgkContext
|
|
19
|
+
from .events import EventSink
|
|
20
|
+
from .gates import GateHub
|
|
21
|
+
|
|
22
|
+
__all__ = ["AgkApp", "AgkContext", "EventSink", "GateHub", "__version__"]
|
|
23
|
+
__version__ = "0.1.0"
|
agk/cli.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""agk CLI — アプリのスキャフォールド生成。
|
|
2
|
+
|
|
3
|
+
使い方::
|
|
4
|
+
|
|
5
|
+
agk new <name> [--lang python|julia]
|
|
6
|
+
[--viewer media|cards|field2d|gltf|mesh|webgpu|none]
|
|
7
|
+
[--dir .]
|
|
8
|
+
|
|
9
|
+
`agk/templates/<lang>/` の ``*.tmpl`` を ``str.format`` 置換
|
|
10
|
+
({name} / {viewer_script} / {viewer_example} / {viewer_note})で展開する。
|
|
11
|
+
テンプレート内の literal な波括弧は ``{{`` / ``}}`` でエスケープする。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
__all__ = ["main"]
|
|
22
|
+
|
|
23
|
+
TEMPLATES_DIR = Path(__file__).resolve().parent / "templates"
|
|
24
|
+
LANGS = ("python", "julia")
|
|
25
|
+
VIEWERS = ("media", "cards", "field2d", "gltf", "mesh", "webgpu", "none")
|
|
26
|
+
|
|
27
|
+
NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
28
|
+
|
|
29
|
+
# viewer 名 → dist 配布モジュール(/agk/ 配下)。none はインポートなし。
|
|
30
|
+
_VIEWER_MODULES = {v: f"agk-viewer-{v}.js" for v in VIEWERS if v != "none"}
|
|
31
|
+
|
|
32
|
+
# viewer 名 → app.py に埋めるコメント付き成果物例(protocol.md §5)。
|
|
33
|
+
_PY_EXAMPLES = {
|
|
34
|
+
"media": '''\
|
|
35
|
+
# --- 成果物(media ビューア: image / video)---------------------------
|
|
36
|
+
# png = ctx.workspace / "plot.png"
|
|
37
|
+
# png.write_bytes(...) # 画像を生成
|
|
38
|
+
# ctx.emit.artifact("image", url="/artifact/plot.png", mime="image/png")
|
|
39
|
+
''',
|
|
40
|
+
"cards": '''\
|
|
41
|
+
# --- 成果物(cards ビューア)------------------------------------------
|
|
42
|
+
# import json
|
|
43
|
+
# cards = [{"title": "result A", "snippet": "...", "badges": ["ok"]}]
|
|
44
|
+
# path = ctx.workspace / "cards.json"
|
|
45
|
+
# path.write_text(json.dumps(cards, ensure_ascii=False), encoding="utf-8")
|
|
46
|
+
# ctx.emit.artifact("cards", url="/artifact/cards.json",
|
|
47
|
+
# mime="application/json", replace=False)
|
|
48
|
+
''',
|
|
49
|
+
"field2d": '''\
|
|
50
|
+
# --- 成果物(field2d ビューア: 2D スカラー場グリッド)------------------
|
|
51
|
+
# import json
|
|
52
|
+
# nx, ny = 32, 32
|
|
53
|
+
# data = {"nx": nx, "ny": ny,
|
|
54
|
+
# "fields": [{"name": "u", "data": [0.0] * (nx * ny)}]}
|
|
55
|
+
# path = ctx.workspace / "field.json"
|
|
56
|
+
# path.write_text(json.dumps(data), encoding="utf-8")
|
|
57
|
+
# ctx.emit.artifact("field2d", url="/artifact/field.json",
|
|
58
|
+
# mime="application/json")
|
|
59
|
+
''',
|
|
60
|
+
"gltf": '''\
|
|
61
|
+
# --- 成果物(gltf ビューア: GLB / glTF)--------------------------------
|
|
62
|
+
# glb = ctx.workspace / "model.glb"
|
|
63
|
+
# glb.write_bytes(...) # GLB を生成
|
|
64
|
+
# ctx.emit.artifact("gltf", url="/artifact/model.glb",
|
|
65
|
+
# mime="model/gltf-binary", meta={"up": "z"})
|
|
66
|
+
''',
|
|
67
|
+
"mesh": '''\
|
|
68
|
+
# --- 成果物(mesh ビューア: FEM メッシュ+結果)-------------------------
|
|
69
|
+
# path = ctx.workspace / "mesh.json"
|
|
70
|
+
# path.write_text(...) # メッシュ+フィールドを書き出す
|
|
71
|
+
# ctx.emit.artifact("mesh", url="/artifact/mesh.json",
|
|
72
|
+
# meta={"fields": [{"name": "stress", "unit": "MPa"}]})
|
|
73
|
+
''',
|
|
74
|
+
"webgpu": '''\
|
|
75
|
+
# --- 成果物(webgpu ビューア: 大規模粒子/格子の GPU 描画)---------------
|
|
76
|
+
# 標準 kind(field2d 等)かアプリ固有 kind("x-<app>.<name>")を送る。
|
|
77
|
+
# ctx.emit.artifact("field2d", url="/artifact/field.json",
|
|
78
|
+
# mime="application/json")
|
|
79
|
+
''',
|
|
80
|
+
"none": '''\
|
|
81
|
+
# --- 成果物(組み込みビューア: markdown / image / video)---------------
|
|
82
|
+
# md = ctx.workspace / "result.md"
|
|
83
|
+
# md.write_text("## 結果\\n", encoding="utf-8")
|
|
84
|
+
# ctx.emit.artifact("markdown", url="/artifact/result.md",
|
|
85
|
+
# mime="text/markdown")
|
|
86
|
+
''',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# viewer 名 → app.jl に埋めるコメント例。
|
|
90
|
+
_JL_EXAMPLES = {
|
|
91
|
+
"none": '''\
|
|
92
|
+
# path = joinpath(ctx.workspace, "result.md")
|
|
93
|
+
# write(path, "## 結果\\n")
|
|
94
|
+
# emit(ctx, "artifact"; kind="markdown", url="/artifact/result.md",
|
|
95
|
+
# mime="text/markdown")''',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _jl_example(viewer: str) -> str:
|
|
100
|
+
if viewer in _JL_EXAMPLES:
|
|
101
|
+
return _JL_EXAMPLES[viewer]
|
|
102
|
+
return (
|
|
103
|
+
f' # emit(ctx, "artifact"; kind="{viewer if viewer != "media" else "image"}",\n'
|
|
104
|
+
f' # url="/artifact/result", mime="application/octet-stream")'
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _viewer_script(viewer: str) -> str:
|
|
109
|
+
"""index.html に挿す viewer モジュールの <script> 行(none は空)。"""
|
|
110
|
+
module = _VIEWER_MODULES.get(viewer)
|
|
111
|
+
if module is None:
|
|
112
|
+
return ""
|
|
113
|
+
return f'<script type="module" src="/agk/{module}"></script>\n'
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _viewer_note(viewer: str) -> str:
|
|
117
|
+
"""README に挿す viewer の補足(none は空)。"""
|
|
118
|
+
module = _VIEWER_MODULES.get(viewer)
|
|
119
|
+
if module is None:
|
|
120
|
+
return ""
|
|
121
|
+
return (
|
|
122
|
+
f"\n## ビューア\n\n"
|
|
123
|
+
f"`{viewer}` ビューア(`/agk/{module}`)を index.html でインポート済み。\n"
|
|
124
|
+
f"対応する artifact イベントの契約は `spec/protocol.md` §5 を参照。\n"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cmd_new(args: argparse.Namespace) -> int:
|
|
129
|
+
if not NAME_RE.match(args.name):
|
|
130
|
+
print(f"agk new: 不正なアプリ名です: {args.name!r} "
|
|
131
|
+
"(英数字で始まり、英数字と . _ - のみ使用可)", file=sys.stderr)
|
|
132
|
+
return 2
|
|
133
|
+
|
|
134
|
+
src_dir = TEMPLATES_DIR / args.lang
|
|
135
|
+
if not src_dir.is_dir():
|
|
136
|
+
print(f"agk new: テンプレートが見つかりません: {src_dir}", file=sys.stderr)
|
|
137
|
+
return 2
|
|
138
|
+
|
|
139
|
+
target = Path(args.dir).expanduser().resolve() / args.name
|
|
140
|
+
if target.exists() and any(target.iterdir()):
|
|
141
|
+
print(f"agk new: 出力先が空ではありません: {target}", file=sys.stderr)
|
|
142
|
+
return 2
|
|
143
|
+
|
|
144
|
+
subs = {
|
|
145
|
+
"name": args.name,
|
|
146
|
+
"viewer_kind": args.viewer,
|
|
147
|
+
"viewer_script": _viewer_script(args.viewer),
|
|
148
|
+
"viewer_note": _viewer_note(args.viewer),
|
|
149
|
+
"viewer_example": (_PY_EXAMPLES[args.viewer] if args.lang == "python"
|
|
150
|
+
else _jl_example(args.viewer)),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
created: list[str] = []
|
|
155
|
+
for tmpl in sorted(src_dir.glob("*.tmpl")):
|
|
156
|
+
out = target / tmpl.name.removesuffix(".tmpl")
|
|
157
|
+
out.write_text(tmpl.read_text(encoding="utf-8").format(**subs),
|
|
158
|
+
encoding="utf-8")
|
|
159
|
+
created.append(out.name)
|
|
160
|
+
|
|
161
|
+
print(f"created: {target}")
|
|
162
|
+
for name in created:
|
|
163
|
+
print(f" {name}")
|
|
164
|
+
return 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main(argv: list[str] | None = None) -> int:
|
|
168
|
+
parser = argparse.ArgumentParser(
|
|
169
|
+
prog="agk", description="app-gui-kit CLI(アプリのスキャフォールド生成)")
|
|
170
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
171
|
+
|
|
172
|
+
new = sub.add_parser("new", help="新規アプリの雛形を生成する")
|
|
173
|
+
new.add_argument("name", help="アプリ名(生成するディレクトリ名)")
|
|
174
|
+
new.add_argument("--lang", choices=LANGS, default="python",
|
|
175
|
+
help="サーバ言語(既定: python)")
|
|
176
|
+
new.add_argument("--viewer", choices=VIEWERS, default="none",
|
|
177
|
+
help="使用する標準ビューア(既定: none)")
|
|
178
|
+
new.add_argument("--dir", default=".",
|
|
179
|
+
help="生成先の親ディレクトリ(既定: カレント)")
|
|
180
|
+
|
|
181
|
+
args = parser.parse_args(argv)
|
|
182
|
+
if args.command == "new":
|
|
183
|
+
return cmd_new(args)
|
|
184
|
+
parser.error(f"unknown command: {args.command}")
|
|
185
|
+
return 2 # unreachable
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
sys.exit(main())
|