zrb 1.0.0a2__py3-none-any.whl → 1.0.0a3__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.
- zrb/__init__.py +48 -39
- zrb/__main__.py +3 -3
- zrb/attr/type.py +2 -1
- zrb/builtin/__init__.py +40 -2
- zrb/builtin/base64.py +32 -0
- zrb/builtin/git.py +156 -0
- zrb/builtin/git_subtree.py +88 -0
- zrb/builtin/group.py +34 -0
- zrb/builtin/llm.py +31 -0
- zrb/builtin/md5.py +34 -0
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/__init__.py +0 -0
- zrb/builtin/project/add/fastapp.py +72 -0
- zrb/builtin/project/add/fastapp_template/.gitignore +4 -0
- zrb/builtin/project/add/fastapp_template/README.md +7 -0
- zrb/builtin/project/add/fastapp_template/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/_zrb/config.py +17 -0
- zrb/builtin/project/add/fastapp_template/_zrb/group.py +16 -0
- zrb/builtin/project/add/fastapp_template/_zrb/helper.py +97 -0
- zrb/builtin/project/add/fastapp_template/_zrb/main.py +132 -0
- zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +22 -0
- zrb/builtin/project/add/fastapp_template/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/common/app.py +18 -0
- zrb/builtin/project/add/fastapp_template/common/db_engine.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/db_repository.py +134 -0
- zrb/builtin/project/add/fastapp_template/common/error.py +8 -0
- zrb/builtin/project/add/fastapp_template/common/schema.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/usecase.py +232 -0
- zrb/builtin/project/add/fastapp_template/config.py +29 -0
- zrb/builtin/project/add/fastapp_template/main.py +7 -0
- zrb/builtin/project/add/fastapp_template/migrate.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +7 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/base_client.py +27 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +9 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/versions/3093c7336477_add_user_table.py +37 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration_metadata.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/route.py +22 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/db_repository.py +39 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +13 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/repository.py +34 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/usecase.py +45 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/versions/.gitkeep +0 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration_metadata.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/route.py +27 -0
- zrb/builtin/project/add/fastapp_template/requirements.txt +6 -0
- zrb/builtin/project/add/fastapp_template/schema/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/schema/role.py +31 -0
- zrb/builtin/project/add/fastapp_template/schema/user.py +31 -0
- zrb/builtin/project/add/fastapp_template/template.env +2 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/project/create/create.py +41 -0
- zrb/builtin/project/create/project-template/README.md +3 -0
- zrb/builtin/project/create/project-template/zrb_init.py +7 -0
- zrb/builtin/python.py +11 -0
- zrb/builtin/shell/__init__.py +0 -5
- zrb/builtin/shell/autocomplete/__init__.py +0 -9
- zrb/builtin/shell/autocomplete/bash.py +5 -6
- zrb/builtin/shell/autocomplete/subcmd.py +7 -8
- zrb/builtin/shell/autocomplete/zsh.py +5 -6
- zrb/builtin/todo.py +186 -0
- zrb/callback/any_callback.py +1 -1
- zrb/callback/callback.py +5 -5
- zrb/cmd/cmd_val.py +2 -2
- zrb/config.py +4 -1
- zrb/content_transformer/any_content_transformer.py +1 -1
- zrb/content_transformer/content_transformer.py +2 -2
- zrb/context/any_context.py +1 -1
- zrb/context/any_shared_context.py +3 -3
- zrb/context/context.py +10 -8
- zrb/context/shared_context.py +9 -8
- zrb/env/__init__.py +0 -3
- zrb/env/any_env.py +1 -1
- zrb/env/env.py +3 -4
- zrb/env/env_file.py +4 -4
- zrb/env/env_map.py +2 -2
- zrb/group/__init__.py +0 -3
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +7 -6
- zrb/input/any_input.py +1 -1
- zrb/input/base_input.py +4 -4
- zrb/input/bool_input.py +5 -5
- zrb/input/float_input.py +3 -3
- zrb/input/int_input.py +3 -3
- zrb/input/option_input.py +51 -0
- zrb/input/password_input.py +2 -2
- zrb/input/str_input.py +1 -1
- zrb/input/text_input.py +12 -10
- zrb/runner/cli.py +79 -45
- zrb/runner/web_app/group_info_ui/controller.py +7 -8
- zrb/runner/web_app/group_info_ui/view.html +2 -2
- zrb/runner/web_app/home_page/controller.py +7 -6
- zrb/runner/web_app/home_page/view.html +2 -2
- zrb/runner/web_app/task_ui/controller.py +8 -12
- zrb/runner/web_app/task_ui/view.html +2 -2
- zrb/runner/web_server.py +137 -211
- zrb/runner/web_util.py +5 -35
- zrb/session/any_session.py +13 -7
- zrb/session/session.py +78 -40
- zrb/session_state_log/session_state_log.py +7 -5
- zrb/session_state_logger/any_session_state_logger.py +1 -1
- zrb/session_state_logger/default_session_state_logger.py +2 -2
- zrb/session_state_logger/file_session_state_logger.py +19 -27
- zrb/task/any_task.py +4 -4
- zrb/task/base_task.py +33 -23
- zrb/task/base_trigger.py +11 -12
- zrb/task/cmd_task.py +48 -39
- zrb/task/http_check.py +8 -8
- zrb/task/llm_task.py +160 -0
- zrb/task/make_task.py +9 -9
- zrb/task/rsync_task.py +7 -7
- zrb/task/scaffolder.py +14 -11
- zrb/task/scheduler.py +6 -7
- zrb/task/task.py +1 -1
- zrb/task/tcp_check.py +8 -8
- zrb/util/attr.py +19 -3
- zrb/util/cli/style.py +71 -2
- zrb/util/cli/subcommand.py +2 -2
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/codemod/add_code_to_class.py +35 -0
- zrb/util/codemod/add_code_to_function.py +36 -0
- zrb/util/codemod/add_code_to_method.py +55 -0
- zrb/util/codemod/add_key_to_dict.py +51 -0
- zrb/util/codemod/add_param_to_function_call.py +39 -0
- zrb/util/codemod/add_property_to_class.py +55 -0
- zrb/util/git.py +156 -0
- zrb/util/git_subtree.py +94 -0
- zrb/util/group.py +2 -2
- zrb/util/llm/tool.py +63 -0
- zrb/util/string/conversion.py +7 -0
- zrb/util/todo.py +135 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/METADATA +8 -5
- zrb-1.0.0a3.dist-info/RECORD +194 -0
- zrb/builtin/shell/_group.py +0 -9
- zrb/builtin/shell/autocomplete/_group.py +0 -6
- zrb/runner/web_app/any_request_handler.py +0 -24
- zrb-1.0.0a2.dist-info/RECORD +0 -120
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/entry_points.txt +0 -0
zrb/runner/web_server.py
CHANGED
@@ -1,224 +1,150 @@
|
|
1
1
|
import asyncio
|
2
|
-
import datetime
|
3
|
-
import json
|
4
2
|
import os
|
5
|
-
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
|
10
|
-
from
|
11
|
-
|
12
|
-
from
|
13
|
-
from
|
14
|
-
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
3
|
+
import sys
|
4
|
+
from contextlib import asynccontextmanager
|
5
|
+
from datetime import datetime, timedelta
|
6
|
+
from typing import Any, Dict, List
|
7
|
+
|
8
|
+
from fastapi import FastAPI, HTTPException, Request
|
9
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
10
|
+
from fastapi.staticfiles import StaticFiles
|
11
|
+
from uvicorn import Config, Server
|
12
|
+
|
13
|
+
from zrb.config import BANNER, WEB_HTTP_PORT
|
14
|
+
from zrb.context.shared_context import SharedContext
|
15
|
+
from zrb.group.any_group import AnyGroup
|
16
|
+
from zrb.runner.web_app.group_info_ui.controller import handle_group_info_ui
|
17
|
+
from zrb.runner.web_app.home_page.controller import handle_home_page
|
18
|
+
from zrb.runner.web_app.task_ui.controller import handle_task_ui
|
19
|
+
from zrb.runner.web_util import NewSessionResponse
|
20
|
+
from zrb.session.session import Session
|
21
|
+
from zrb.session_state_log.session_state_log import SessionStateLog, SessionStateLogList
|
22
|
+
from zrb.session_state_logger.default_session_state_logger import (
|
18
23
|
default_session_state_logger,
|
19
24
|
)
|
20
|
-
from
|
21
|
-
from
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
)
|
62
|
-
|
63
|
-
|
25
|
+
from zrb.task.any_task import AnyTask
|
26
|
+
from zrb.util.group import extract_node_from_args, get_node_path
|
27
|
+
|
28
|
+
|
29
|
+
def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
30
|
+
_STATIC_DIR = os.path.join(os.path.dirname(__file__), "web_app", "static")
|
31
|
+
_COROS = []
|
32
|
+
|
33
|
+
@asynccontextmanager
|
34
|
+
async def lifespan(app: FastAPI):
|
35
|
+
for line in BANNER.split("\n") + [
|
36
|
+
f"Zrb Server running on http://localhost:{port}"
|
37
|
+
]:
|
38
|
+
print(line, file=sys.stderr)
|
39
|
+
yield
|
40
|
+
for coro in _COROS:
|
41
|
+
coro.cancel()
|
42
|
+
asyncio.gather(*_COROS)
|
43
|
+
|
44
|
+
app = FastAPI(title="zrb", lifespan=lifespan)
|
45
|
+
|
46
|
+
# Serve static files
|
47
|
+
app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
|
48
|
+
|
49
|
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
50
|
+
@app.get("/ui", response_class=HTMLResponse, include_in_schema=False)
|
51
|
+
@app.get("/ui/", response_class=HTMLResponse, include_in_schema=False)
|
52
|
+
async def home_page():
|
53
|
+
return handle_home_page(root_group)
|
54
|
+
|
55
|
+
@app.get("/static/{file_path:path}", include_in_schema=False)
|
56
|
+
async def static_files(file_path: str):
|
57
|
+
full_path = os.path.join(_STATIC_DIR, file_path)
|
58
|
+
if os.path.isfile(full_path):
|
59
|
+
return FileResponse(full_path)
|
60
|
+
raise HTTPException(status_code=404, detail="File not found")
|
61
|
+
|
62
|
+
@app.get("/ui/{path:path}", include_in_schema=False)
|
63
|
+
async def ui_page(path: str):
|
64
|
+
# Avoid capturing '/ui' itself
|
65
|
+
if not path:
|
66
|
+
raise HTTPException(status_code=404, detail="Not Found")
|
67
|
+
args = path.split("/")
|
68
|
+
node, node_path, residual_args = extract_node_from_args(root_group, args)
|
69
|
+
url = f"/ui/{'/'.join(node_path)}/"
|
70
|
+
if isinstance(node, AnyTask):
|
71
|
+
shared_ctx = SharedContext(env=dict(os.environ))
|
72
|
+
session = Session(shared_ctx=shared_ctx, root_group=root_group)
|
73
|
+
return handle_task_ui(root_group, node, session, url, residual_args)
|
74
|
+
elif isinstance(node, AnyGroup):
|
75
|
+
return handle_group_info_ui(root_group, node, url)
|
76
|
+
raise HTTPException(status_code=404, detail="Not Found")
|
77
|
+
|
78
|
+
@app.post("/api/{path:path}")
|
79
|
+
async def create_new_session(
|
80
|
+
path: str, request: Request = None
|
81
|
+
) -> NewSessionResponse:
|
82
|
+
"""
|
83
|
+
Creating new session
|
84
|
+
"""
|
85
|
+
args = path.split("/")
|
86
|
+
node, _, residual_args = extract_node_from_args(root_group, args)
|
87
|
+
if isinstance(node, AnyTask):
|
88
|
+
session_name = residual_args[0] if residual_args else None
|
89
|
+
if not session_name:
|
90
|
+
body = await request.json()
|
64
91
|
shared_ctx = SharedContext(env=dict(os.environ))
|
65
|
-
session = Session(shared_ctx=shared_ctx, root_group=
|
66
|
-
|
67
|
-
|
68
|
-
)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
92
|
+
session = Session(shared_ctx=shared_ctx, root_group=root_group)
|
93
|
+
coro = asyncio.create_task(node.async_run(session, str_kwargs=body))
|
94
|
+
_COROS.append(coro)
|
95
|
+
coro.add_done_callback(lambda coro: _COROS.remove(coro))
|
96
|
+
return NewSessionResponse(session_name=session.name)
|
97
|
+
raise HTTPException(status_code=404, detail="Not Found")
|
98
|
+
|
99
|
+
@app.get("/api/{path:path}", response_model=SessionStateLog | SessionStateLogList)
|
100
|
+
async def get_session(path: str, query_params: Dict[str, Any] = {}):
|
101
|
+
"""
|
102
|
+
Getting existing session or sessions
|
103
|
+
"""
|
104
|
+
args = path.split("/")
|
105
|
+
node, _, residual_args = extract_node_from_args(root_group, args)
|
106
|
+
if isinstance(node, AnyTask) and residual_args:
|
107
|
+
if residual_args[0] == "list":
|
108
|
+
task_path = get_node_path(root_group, node)
|
109
|
+
return list_sessions(task_path, query_params)
|
83
110
|
else:
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
str_kwargs: dict[str, str] = self.read_json_request()
|
98
|
-
shared_ctx = SharedContext(env=dict(os.environ))
|
99
|
-
session = Session(shared_ctx=shared_ctx, root_group=self._root_group)
|
100
|
-
coro = asyncio.run_coroutine_threadsafe(
|
101
|
-
task.async_run(session, str_kwargs=str_kwargs),
|
102
|
-
self._event_loop,
|
103
|
-
)
|
104
|
-
self._coros.append(coro)
|
105
|
-
coro.add_done_callback(lambda coro: self._coros.remove(coro))
|
106
|
-
self.send_json_response({"session_name": session.name})
|
107
|
-
else:
|
108
|
-
self.send_error(404, "Not Found")
|
109
|
-
|
110
|
-
def send_session_list_json_data(
|
111
|
-
self, task_path: list[str], query_params: dict[str, list[Any]]
|
112
|
-
):
|
113
|
-
print(query_params)
|
114
|
-
max_start_time = datetime.datetime.now()
|
115
|
-
if "to" in query_params and len(query_params["to"]) > 0:
|
116
|
-
max_start_time = datetime.datetime.strptime(
|
117
|
-
query_params["to"][0], "%Y-%m-%d %H:%M:%S"
|
118
|
-
)
|
119
|
-
min_start_time = max_start_time - datetime.timedelta(hours=1)
|
120
|
-
if "from" in query_params and len(query_params["from"]) > 0:
|
121
|
-
min_start_time = datetime.datetime.strptime(
|
122
|
-
query_params["from"][0], "%Y-%m-%d %H:%M:%S"
|
111
|
+
return read_session(residual_args[0])
|
112
|
+
raise HTTPException(status_code=404, detail="Not Found")
|
113
|
+
|
114
|
+
def list_sessions(
|
115
|
+
task_path: List[str], query_params: Dict[str, Any]
|
116
|
+
) -> SessionStateLogList:
|
117
|
+
max_start_time = datetime.now()
|
118
|
+
if "to" in query_params:
|
119
|
+
max_start_time = datetime.strptime(query_params["to"], "%Y-%m-%d %H:%M:%S")
|
120
|
+
min_start_time = max_start_time - timedelta(hours=1)
|
121
|
+
if "from" in query_params:
|
122
|
+
min_start_time = datetime.strptime(
|
123
|
+
query_params["from"], "%Y-%m-%d %H:%M:%S"
|
123
124
|
)
|
124
|
-
page = 0
|
125
|
-
|
126
|
-
page = int(query_params["page"][0])
|
127
|
-
limit = 10
|
128
|
-
if "limit" in query_params and len(query_params["limit"]) > 0:
|
129
|
-
limit = int(query_params["limit"][0])
|
125
|
+
page = int(query_params.get("page", 0))
|
126
|
+
limit = int(query_params.get("limit", 10))
|
130
127
|
try:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
limit=limit,
|
138
|
-
)
|
128
|
+
return default_session_state_logger.list(
|
129
|
+
task_path,
|
130
|
+
min_start_time=min_start_time,
|
131
|
+
max_start_time=max_start_time,
|
132
|
+
page=page,
|
133
|
+
limit=limit,
|
139
134
|
)
|
140
135
|
except Exception as e:
|
141
|
-
|
136
|
+
raise HTTPException(status_code=500, detail=str(e))
|
142
137
|
|
143
|
-
def
|
138
|
+
def read_session(session_name: str) -> SessionStateLog:
|
144
139
|
try:
|
145
|
-
|
140
|
+
return default_session_state_logger.read(session_name)
|
146
141
|
except Exception as e:
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
self.send_response(http_status)
|
157
|
-
self.send_header("Content-type", "text/html")
|
158
|
-
self.end_headers()
|
159
|
-
self.wfile.write(f"{html}".encode())
|
160
|
-
|
161
|
-
def send_css_response(self, css_path: str):
|
162
|
-
self.send_response(200)
|
163
|
-
self.send_header("Content-type", "text/css")
|
164
|
-
self.end_headers()
|
165
|
-
# If css_content is provided, send it; otherwise, you could read from a file here
|
166
|
-
with open(css_path, "r") as f:
|
167
|
-
css_content = f.read()
|
168
|
-
self.wfile.write(css_content.encode())
|
169
|
-
|
170
|
-
def send_image_response(self, image_path: str):
|
171
|
-
try:
|
172
|
-
with open(image_path, "rb") as img:
|
173
|
-
self.send_response(200)
|
174
|
-
if image_path.endswith(".png"):
|
175
|
-
self.send_header("Content-type", "image/png")
|
176
|
-
elif image_path.endswith(".jpg") or image_path.endswith(".jpeg"):
|
177
|
-
self.send_header("Content-type", "image/jpeg")
|
178
|
-
self.end_headers()
|
179
|
-
self.wfile.write(img.read())
|
180
|
-
except FileNotFoundError:
|
181
|
-
self.send_error(404, "Image Not Found")
|
182
|
-
|
183
|
-
def read_json_request(self) -> Any:
|
184
|
-
content_length = int(self.headers["Content-Length"])
|
185
|
-
post_data = self.rfile.read(content_length)
|
186
|
-
try:
|
187
|
-
return json.loads(post_data.decode())
|
188
|
-
except json.JSONDecodeError:
|
189
|
-
self.send_json_response({"error": "Invalid JSON"}, http_status=400)
|
190
|
-
return None
|
191
|
-
|
192
|
-
|
193
|
-
def run_web_server(ctx: AnyContext, root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
194
|
-
server_address = ("", port)
|
195
|
-
event_loop = asyncio.new_event_loop()
|
196
|
-
coros = []
|
197
|
-
# Use functools.partial to bind the custom attribute
|
198
|
-
handler = partial(
|
199
|
-
WebRequestHandler,
|
200
|
-
root_group=root_group,
|
201
|
-
event_loop=event_loop,
|
202
|
-
session_dir=SESSION_LOG_DIR,
|
203
|
-
coros=coros,
|
204
|
-
)
|
205
|
-
httpd = ThreadingHTTPServer(server_address, handler)
|
206
|
-
banner_lines = BANNER.split("\n") + [
|
207
|
-
f"Zrb Server running on http://localhost:{port}"
|
208
|
-
]
|
209
|
-
for line in banner_lines:
|
210
|
-
ctx.print(line)
|
211
|
-
loop_thread = Thread(target=start_event_loop, args=[event_loop], daemon=True)
|
212
|
-
loop_thread.start()
|
213
|
-
try:
|
214
|
-
httpd.serve_forever()
|
215
|
-
finally:
|
216
|
-
for coro in coros:
|
217
|
-
coro.cancel()
|
218
|
-
httpd.shutdown()
|
219
|
-
httpd.server_close()
|
220
|
-
# Cancel all tasks
|
221
|
-
for task in asyncio.all_tasks(event_loop):
|
222
|
-
task.cancel()
|
223
|
-
event_loop.call_soon_threadsafe(event_loop.stop)
|
224
|
-
loop_thread.join()
|
142
|
+
raise HTTPException(status_code=500, detail=str(e))
|
143
|
+
|
144
|
+
return app
|
145
|
+
|
146
|
+
|
147
|
+
async def run_web_server(app: FastAPI, port: int = WEB_HTTP_PORT):
|
148
|
+
config = Config(app=app, host="0.0.0.0", port=port, loop="asyncio")
|
149
|
+
server = Server(config)
|
150
|
+
await server.serve()
|
zrb/runner/web_util.py
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
import
|
1
|
+
from pydantic import BaseModel
|
2
|
+
|
3
|
+
|
4
|
+
class NewSessionResponse(BaseModel):
|
5
|
+
session_name: str
|
2
6
|
|
3
7
|
|
4
8
|
def url_to_args(url: str) -> list[str]:
|
@@ -10,37 +14,3 @@ def node_path_to_url(args: list[str]) -> str:
|
|
10
14
|
pruned_args = [part for part in args if part.strip() != ""]
|
11
15
|
stripped_url = "/".join(pruned_args)
|
12
16
|
return f"/{stripped_url}/"
|
13
|
-
|
14
|
-
|
15
|
-
class SessionSnapshotCondition:
|
16
|
-
def __init__(self):
|
17
|
-
self._should_stop = False
|
18
|
-
|
19
|
-
@property
|
20
|
-
def should_stop(self) -> bool:
|
21
|
-
return self._should_stop
|
22
|
-
|
23
|
-
def stop(self):
|
24
|
-
self._should_stop = True
|
25
|
-
|
26
|
-
|
27
|
-
def start_event_loop(event_loop: asyncio.AbstractEventLoop):
|
28
|
-
asyncio.set_event_loop(event_loop)
|
29
|
-
event_loop.run_forever()
|
30
|
-
|
31
|
-
|
32
|
-
async def shutdown_event_loop(event_loop: asyncio.AbstractEventLoop):
|
33
|
-
tasks = [
|
34
|
-
t
|
35
|
-
for t in asyncio.all_tasks(event_loop)
|
36
|
-
if not t.done() and t is not asyncio.current_task(event_loop)
|
37
|
-
]
|
38
|
-
print("TASKS", tasks)
|
39
|
-
for task in tasks:
|
40
|
-
print("CANCEL TASK", task)
|
41
|
-
task.cancel()
|
42
|
-
print("CANCELED", task)
|
43
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
44
|
-
print("DONE")
|
45
|
-
event_loop.stop()
|
46
|
-
print("LOOP STOP")
|
zrb/session/any_session.py
CHANGED
@@ -3,15 +3,15 @@ from __future__ import annotations # Enables forward references
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from typing import TYPE_CHECKING, Any, Coroutine, TypeVar
|
5
5
|
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
6
|
+
from zrb.context.any_context import AnyContext
|
7
|
+
from zrb.group.any_group import AnyGroup
|
8
|
+
from zrb.session_state_log.session_state_log import SessionStateLog
|
9
|
+
from zrb.session_state_logger.any_session_state_logger import AnySessionStateLogger
|
10
|
+
from zrb.task_status.task_status import TaskStatus
|
11
11
|
|
12
12
|
if TYPE_CHECKING:
|
13
|
-
from
|
14
|
-
from
|
13
|
+
from zrb.context import any_shared_context
|
14
|
+
from zrb.task import any_task
|
15
15
|
|
16
16
|
TAnySession = TypeVar("TAnySession", bound="AnySession")
|
17
17
|
|
@@ -36,6 +36,12 @@ class AnySession(ABC):
|
|
36
36
|
"""Session root group"""
|
37
37
|
pass
|
38
38
|
|
39
|
+
@property
|
40
|
+
@abstractmethod
|
41
|
+
def task_names(self) -> list[str]:
|
42
|
+
"""Task names in this session"""
|
43
|
+
pass
|
44
|
+
|
39
45
|
@property
|
40
46
|
@abstractmethod
|
41
47
|
def shared_ctx(self) -> any_shared_context.AnySharedContext:
|
zrb/session/session.py
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
import asyncio
|
2
2
|
from typing import Any, Coroutine
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
|
4
|
+
from zrb.context.any_shared_context import AnySharedContext
|
5
|
+
from zrb.context.context import AnyContext, Context
|
6
|
+
from zrb.group.any_group import AnyGroup
|
7
|
+
from zrb.session.any_session import AnySession
|
8
|
+
from zrb.session_state_log.session_state_log import (
|
9
|
+
SessionStateLog,
|
10
|
+
TaskStatusHistoryStateLog,
|
11
|
+
TaskStatusStateLog,
|
12
|
+
)
|
13
|
+
from zrb.session_state_logger.any_session_state_logger import AnySessionStateLogger
|
14
|
+
from zrb.session_state_logger.default_session_state_logger import (
|
10
15
|
default_session_state_logger,
|
11
16
|
)
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
from zrb.task.any_task import AnyTask
|
18
|
+
from zrb.task_status.task_status import TaskStatus
|
19
|
+
from zrb.util.cli.style import (
|
20
|
+
BLUE,
|
21
|
+
BRIGHT_BLUE,
|
22
|
+
BRIGHT_CYAN,
|
23
|
+
BRIGHT_GREEN,
|
24
|
+
BRIGHT_MAGENTA,
|
25
|
+
BRIGHT_YELLOW,
|
26
|
+
CYAN,
|
27
|
+
GREEN,
|
28
|
+
ICONS,
|
29
|
+
MAGENTA,
|
30
|
+
YELLOW,
|
31
|
+
)
|
32
|
+
from zrb.util.group import get_node_path
|
33
|
+
from zrb.util.string.name import get_random_name
|
34
|
+
from zrb.xcom.xcom import Xcom
|
19
35
|
|
20
36
|
|
21
37
|
class Session(AnySession):
|
@@ -39,7 +55,18 @@ class Session(AnySession):
|
|
39
55
|
self._action_coros: dict[AnyTask, Coroutine] = {}
|
40
56
|
self._monitoring_coros: dict[AnyTask, Coroutine] = {}
|
41
57
|
self._coros: list[Coroutine] = []
|
42
|
-
self._colors = [
|
58
|
+
self._colors = [
|
59
|
+
GREEN,
|
60
|
+
YELLOW,
|
61
|
+
BLUE,
|
62
|
+
MAGENTA,
|
63
|
+
CYAN,
|
64
|
+
BRIGHT_GREEN,
|
65
|
+
BRIGHT_YELLOW,
|
66
|
+
BRIGHT_BLUE,
|
67
|
+
BRIGHT_MAGENTA,
|
68
|
+
BRIGHT_CYAN,
|
69
|
+
]
|
43
70
|
self._icons = ICONS
|
44
71
|
self._color_index = 0
|
45
72
|
self._icon_index = 0
|
@@ -62,6 +89,10 @@ class Session(AnySession):
|
|
62
89
|
def root_group(self) -> AnyGroup | None:
|
63
90
|
return self._root_group
|
64
91
|
|
92
|
+
@property
|
93
|
+
def task_names(self) -> list[str]:
|
94
|
+
return [task.name for task in self._task_status.keys()]
|
95
|
+
|
65
96
|
@property
|
66
97
|
def shared_ctx(self) -> AnySharedContext:
|
67
98
|
return self._shared_ctx
|
@@ -105,35 +136,42 @@ class Session(AnySession):
|
|
105
136
|
|
106
137
|
def as_state_log(self) -> SessionStateLog:
|
107
138
|
task_status_log: dict[str, TaskStatusStateLog] = {}
|
139
|
+
log_start_time = ""
|
108
140
|
for task, task_status in self._task_status.items():
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
"
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
141
|
+
history_log = [
|
142
|
+
TaskStatusHistoryStateLog(
|
143
|
+
status=status,
|
144
|
+
time=status_at.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
145
|
+
)
|
146
|
+
for status, status_at in task_status.history
|
147
|
+
]
|
148
|
+
if len(history_log) > 0 and (
|
149
|
+
log_start_time == "" or history_log[0].time < log_start_time
|
150
|
+
):
|
151
|
+
log_start_time = history_log[0].time
|
152
|
+
task_status_log[task.name] = TaskStatusStateLog(
|
153
|
+
is_started=task_status.is_started,
|
154
|
+
is_ready=task_status.is_ready,
|
155
|
+
is_completed=task_status.is_completed,
|
156
|
+
is_skipped=task_status.is_skipped,
|
157
|
+
is_failed=task_status.is_failed,
|
158
|
+
is_permanently_failed=task_status.is_permanently_failed,
|
159
|
+
is_terminated=task_status.is_terminated,
|
160
|
+
history=history_log,
|
161
|
+
)
|
162
|
+
return SessionStateLog(
|
163
|
+
name=self.name,
|
164
|
+
start_time=log_start_time,
|
165
|
+
main_task_name=self._main_task.name,
|
166
|
+
path=self.task_path,
|
167
|
+
final_result=(
|
130
168
|
f"{self.final_result}" if self.final_result is not None else ""
|
131
169
|
),
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
170
|
+
finished=self.is_terminated,
|
171
|
+
log=self.shared_ctx.shared_log,
|
172
|
+
input=self.shared_ctx.input,
|
173
|
+
task_status=task_status_log,
|
174
|
+
)
|
137
175
|
|
138
176
|
def get_ctx(self, task: AnyTask) -> AnyContext:
|
139
177
|
self._register_single_task(task)
|
@@ -1,12 +1,14 @@
|
|
1
|
-
from typing import Any
|
1
|
+
from typing import Any
|
2
2
|
|
3
|
+
from pydantic import BaseModel
|
3
4
|
|
4
|
-
|
5
|
+
|
6
|
+
class TaskStatusHistoryStateLog(BaseModel):
|
5
7
|
status: str
|
6
8
|
time: str
|
7
9
|
|
8
10
|
|
9
|
-
class TaskStatusStateLog(
|
11
|
+
class TaskStatusStateLog(BaseModel):
|
10
12
|
history: list[TaskStatusHistoryStateLog]
|
11
13
|
is_started: bool
|
12
14
|
is_ready: bool
|
@@ -17,7 +19,7 @@ class TaskStatusStateLog(TypedDict):
|
|
17
19
|
is_terminated: bool
|
18
20
|
|
19
21
|
|
20
|
-
class SessionStateLog(
|
22
|
+
class SessionStateLog(BaseModel):
|
21
23
|
name: str
|
22
24
|
start_time: str
|
23
25
|
main_task_name: str
|
@@ -29,6 +31,6 @@ class SessionStateLog(TypedDict):
|
|
29
31
|
task_status: dict[str, TaskStatusStateLog]
|
30
32
|
|
31
33
|
|
32
|
-
class SessionStateLogList(
|
34
|
+
class SessionStateLogList(BaseModel):
|
33
35
|
total: int
|
34
36
|
data: list[SessionStateLog]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import datetime
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
|
4
|
-
from
|
4
|
+
from zrb.session_state_log.session_state_log import SessionStateLog, SessionStateLogList
|
5
5
|
|
6
6
|
|
7
7
|
class AnySessionStateLogger(ABC):
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from
|
2
|
-
from .file_session_state_logger import FileSessionStateLogger
|
1
|
+
from zrb.config import SESSION_LOG_DIR
|
2
|
+
from zrb.session_state_logger.file_session_state_logger import FileSessionStateLogger
|
3
3
|
|
4
4
|
default_session_state_logger = FileSessionStateLogger(SESSION_LOG_DIR)
|