zrb 1.0.0a2__py3-none-any.whl → 1.0.0a4__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 +49 -40
- zrb/__main__.py +5 -3
- zrb/attr/type.py +2 -1
- zrb/builtin/__init__.py +42 -2
- zrb/builtin/base64.py +34 -0
- zrb/builtin/git.py +156 -0
- zrb/builtin/git_subtree.py +88 -0
- zrb/builtin/group.py +34 -0
- zrb/builtin/llm/llm_chat.py +47 -0
- zrb/builtin/llm/tool/cli.py +9 -0
- zrb/builtin/llm/tool/rag.py +189 -0
- zrb/builtin/llm/tool/web.py +74 -0
- zrb/builtin/md5.py +36 -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/_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/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 +219 -0
- zrb/callback/any_callback.py +1 -1
- zrb/callback/callback.py +5 -5
- zrb/cmd/cmd_val.py +2 -2
- zrb/config.py +16 -3
- 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 +80 -45
- zrb/runner/web_app.py +150 -0
- zrb/runner/web_controller/__init__.py +0 -0
- zrb/runner/web_controller/group_info_ui/__init__.py +0 -0
- zrb/runner/{web_app → web_controller}/group_info_ui/controller.py +7 -8
- zrb/runner/{web_app → web_controller}/group_info_ui/view.html +2 -2
- zrb/runner/web_controller/home_page/__init__.py +0 -0
- zrb/runner/{web_app → web_controller}/home_page/controller.py +7 -6
- zrb/runner/{web_app → web_controller}/home_page/view.html +2 -2
- zrb/runner/web_controller/task_ui/__init__.py +0 -0
- zrb/runner/{web_app → web_controller}/task_ui/controller.py +8 -12
- zrb/runner/{web_app → web_controller}/task_ui/view.html +2 -2
- 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 +72 -65
- zrb/task/http_check.py +13 -13
- zrb/task/llm_task.py +215 -0
- zrb/task/make_task.py +9 -9
- zrb/task/rsync_task.py +25 -25
- zrb/task/scaffolder.py +18 -15
- zrb/task/scheduler.py +6 -7
- zrb/task/task.py +1 -1
- zrb/task/tcp_check.py +11 -13
- 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 +259 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a4.dist-info}/METADATA +13 -5
- zrb-1.0.0a4.dist-info/RECORD +197 -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/runner/web_server.py +0 -224
- zrb-1.0.0a2.dist-info/RECORD +0 -120
- /zrb/{runner/web_app → builtin/project}/__init__.py +0 -0
- /zrb/{runner/web_app/group_info_ui → builtin/project/add}/__init__.py +0 -0
- /zrb/{runner/web_app/home_page → builtin/project/add/fastapp_template}/__init__.py +0 -0
- /zrb/{runner/web_app/task_ui → builtin/project/add/fastapp_template/common}/__init__.py +0 -0
- /zrb/runner/{web_app → web_controller}/group_info_ui/partial/group_info.html +0 -0
- /zrb/runner/{web_app → web_controller}/group_info_ui/partial/group_li.html +0 -0
- /zrb/runner/{web_app → web_controller}/group_info_ui/partial/task_info.html +0 -0
- /zrb/runner/{web_app → web_controller}/group_info_ui/partial/task_li.html +0 -0
- /zrb/runner/{web_app → web_controller}/home_page/partial/group_info.html +0 -0
- /zrb/runner/{web_app → web_controller}/home_page/partial/group_li.html +0 -0
- /zrb/runner/{web_app → web_controller}/home_page/partial/task_info.html +0 -0
- /zrb/runner/{web_app → web_controller}/home_page/partial/task_li.html +0 -0
- /zrb/runner/{web_app → web_controller}/static/favicon-32x32.png +0 -0
- /zrb/runner/{web_app → web_controller}/static/pico.min.css +0 -0
- /zrb/runner/{web_app → web_controller}/task_ui/partial/common-util.js +0 -0
- /zrb/runner/{web_app → web_controller}/task_ui/partial/input.html +0 -0
- /zrb/runner/{web_app → web_controller}/task_ui/partial/main.js +0 -0
- /zrb/runner/{web_app → web_controller}/task_ui/partial/show-existing-session.js +0 -0
- /zrb/runner/{web_app → web_controller}/task_ui/partial/visualize-history.js +0 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a4.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/env/env_map.py
CHANGED
zrb/group/__init__.py
CHANGED
zrb/group/any_group.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Optional, Union
|
3
3
|
|
4
|
-
from
|
4
|
+
from zrb.task.any_task import AnyTask
|
5
5
|
|
6
6
|
|
7
7
|
class AnyGroup(ABC):
|
@@ -36,7 +36,7 @@ class AnyGroup(ABC):
|
|
36
36
|
pass
|
37
37
|
|
38
38
|
@abstractmethod
|
39
|
-
def add_group(self, group: "AnyGroup") -> "AnyGroup":
|
39
|
+
def add_group(self, group: Union["AnyGroup", str]) -> "AnyGroup":
|
40
40
|
pass
|
41
41
|
|
42
42
|
@abstractmethod
|
zrb/group/group.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
from
|
2
|
-
from .
|
1
|
+
from zrb.group.any_group import AnyGroup
|
2
|
+
from zrb.task.any_task import AnyTask
|
3
3
|
|
4
4
|
|
5
5
|
class Group(AnyGroup):
|
@@ -42,10 +42,11 @@ class Group(AnyGroup):
|
|
42
42
|
alias.sort()
|
43
43
|
return {name: self._tasks.get(name) for name in alias}
|
44
44
|
|
45
|
-
def add_group(self, group: AnyGroup, alias: str | None = None) -> AnyGroup:
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def add_group(self, group: AnyGroup | str, alias: str | None = None) -> AnyGroup:
|
46
|
+
real_group = Group(group) if isinstance(group, str) else group
|
47
|
+
alias = alias if alias is not None else real_group.name
|
48
|
+
self._groups[alias] = real_group
|
49
|
+
return real_group
|
49
50
|
|
50
51
|
def add_task(self, task: AnyTask, alias: str | None = None) -> AnyTask:
|
51
52
|
alias = alias if alias is not None else task.name
|
zrb/input/any_input.py
CHANGED
zrb/input/base_input.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from .
|
3
|
+
from zrb.attr.type import StrAttr
|
4
|
+
from zrb.context.any_shared_context import AnySharedContext
|
5
|
+
from zrb.input.any_input import AnyInput
|
6
|
+
from zrb.util.attr import get_str_attr
|
7
7
|
|
8
8
|
|
9
9
|
class BaseInput(AnyInput):
|
zrb/input/bool_input.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
from
|
2
|
-
from
|
3
|
-
from
|
4
|
-
from .
|
1
|
+
from zrb.attr.type import StrAttr
|
2
|
+
from zrb.context.any_shared_context import AnySharedContext
|
3
|
+
from zrb.input.base_input import BaseInput
|
4
|
+
from zrb.util.string.conversion import to_boolean
|
5
5
|
|
6
6
|
|
7
|
-
class
|
7
|
+
class BoolInput(BaseInput):
|
8
8
|
def __init__(
|
9
9
|
self,
|
10
10
|
name: str,
|
zrb/input/float_input.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from
|
2
|
-
from
|
3
|
-
from .base_input import BaseInput
|
1
|
+
from zrb.attr.type import StrAttr
|
2
|
+
from zrb.context.any_shared_context import AnySharedContext
|
3
|
+
from zrb.input.base_input import BaseInput
|
4
4
|
|
5
5
|
|
6
6
|
class FloatInput(BaseInput):
|
zrb/input/int_input.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from
|
2
|
-
from
|
3
|
-
from .base_input import BaseInput
|
1
|
+
from zrb.attr.type import StrAttr
|
2
|
+
from zrb.context.any_shared_context import AnySharedContext
|
3
|
+
from zrb.input.base_input import BaseInput
|
4
4
|
|
5
5
|
|
6
6
|
class IntInput(BaseInput):
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from zrb.attr.type import StrAttr, StrListAttr
|
2
|
+
from zrb.context.any_shared_context import AnySharedContext
|
3
|
+
from zrb.input.base_input import BaseInput
|
4
|
+
from zrb.util.attr import get_str_list_attr
|
5
|
+
|
6
|
+
|
7
|
+
class OptionInput(BaseInput):
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
name: str,
|
11
|
+
description: str | None = None,
|
12
|
+
prompt: str | None = None,
|
13
|
+
options: StrListAttr = [],
|
14
|
+
default_str: StrAttr = "",
|
15
|
+
auto_render: bool = True,
|
16
|
+
allow_empty: bool = True,
|
17
|
+
):
|
18
|
+
super().__init__(
|
19
|
+
name=name,
|
20
|
+
description=description,
|
21
|
+
prompt=prompt,
|
22
|
+
default_str=default_str,
|
23
|
+
auto_render=auto_render,
|
24
|
+
allow_empty=allow_empty,
|
25
|
+
)
|
26
|
+
self._options = options
|
27
|
+
|
28
|
+
def to_html(self, ctx: AnySharedContext) -> str:
|
29
|
+
name = self.name
|
30
|
+
description = self.description
|
31
|
+
default = self._get_default_str(ctx)
|
32
|
+
html = [f'<select name="{name}" placeholder="{description}">']
|
33
|
+
for value in get_str_list_attr(ctx, self._options, self._auto_render):
|
34
|
+
selected = "selected" if value == default else ""
|
35
|
+
html.append(f'<option value="{value}" {selected}>{value}</option>')
|
36
|
+
html.append("</select>")
|
37
|
+
return "\n".join(html)
|
38
|
+
|
39
|
+
def _prompt_cli_str(self, shared_ctx: AnySharedContext) -> str:
|
40
|
+
prompt_message = self.prompt_message
|
41
|
+
default_value = self._get_default_str(shared_ctx)
|
42
|
+
options = get_str_list_attr(shared_ctx, self._options, self._auto_render)
|
43
|
+
option_str = ", ".join(options)
|
44
|
+
if default_value != "":
|
45
|
+
prompt_message = f"{prompt_message} ({option_str}) [{default_value}]"
|
46
|
+
value = input(f"{prompt_message}: ")
|
47
|
+
if value.strip() != "" and value.strip() not in options:
|
48
|
+
value = self._prompt_cli_str(shared_ctx)
|
49
|
+
if value.strip() == "":
|
50
|
+
value = default_value
|
51
|
+
return value
|
zrb/input/password_input.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
import getpass
|
2
2
|
from collections.abc import Callable
|
3
3
|
|
4
|
-
from
|
5
|
-
from .base_input import BaseInput
|
4
|
+
from zrb.context.any_shared_context import AnySharedContext
|
5
|
+
from zrb.input.base_input import BaseInput
|
6
6
|
|
7
7
|
|
8
8
|
class PasswordInput(BaseInput):
|
zrb/input/str_input.py
CHANGED
zrb/input/text_input.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
import os
|
1
2
|
import subprocess
|
2
3
|
import tempfile
|
3
4
|
from collections.abc import Callable
|
4
5
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from .base_input import BaseInput
|
6
|
+
from zrb.config import DEFAULT_EDITOR
|
7
|
+
from zrb.context.any_shared_context import AnySharedContext
|
8
|
+
from zrb.input.base_input import BaseInput
|
8
9
|
|
9
10
|
|
10
11
|
class TextInput(BaseInput):
|
@@ -66,14 +67,15 @@ class TextInput(BaseInput):
|
|
66
67
|
|
67
68
|
def _prompt_cli_str(self, shared_ctx: AnySharedContext) -> str:
|
68
69
|
prompt_message = (
|
69
|
-
f"{self.comment_start}{super().prompt_message}{self.comment_end}
|
70
|
+
f"{self.comment_start}{super().prompt_message}{self.comment_end}"
|
70
71
|
)
|
72
|
+
prompt_message_eol = f"{prompt_message}\n"
|
71
73
|
default_value = self._get_default_str(shared_ctx)
|
72
74
|
with tempfile.NamedTemporaryFile(
|
73
75
|
delete=False, suffix=self._extension
|
74
76
|
) as temp_file:
|
75
77
|
temp_file_name = temp_file.name
|
76
|
-
temp_file.write(
|
78
|
+
temp_file.write(prompt_message_eol.encode())
|
77
79
|
# Pre-fill with default content
|
78
80
|
if default_value:
|
79
81
|
temp_file.write(default_value.encode())
|
@@ -82,8 +84,8 @@ class TextInput(BaseInput):
|
|
82
84
|
subprocess.call([self._editor, temp_file_name])
|
83
85
|
# Read the edited content
|
84
86
|
with open(temp_file_name, "r") as temp_file:
|
85
|
-
edited_content = temp_file.read()
|
86
|
-
parts = edited_content.split(prompt_message)
|
87
|
-
|
88
|
-
|
89
|
-
return edited_content
|
87
|
+
edited_content = temp_file.read()
|
88
|
+
parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
|
89
|
+
edited_content = "\n".join(parts).lstrip()
|
90
|
+
os.remove(temp_file_name)
|
91
|
+
return edited_content
|
zrb/runner/cli.py
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
import sys
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
|
4
|
+
from zrb.config import BANNER, WEB_HTTP_PORT
|
5
|
+
from zrb.context.any_context import AnyContext
|
6
|
+
from zrb.context.shared_context import SharedContext
|
7
|
+
from zrb.group.group import Group
|
8
|
+
from zrb.runner.web_app import create_app
|
9
|
+
from zrb.session.session import Session
|
10
|
+
from zrb.task.any_task import AnyTask
|
11
|
+
from zrb.task.make_task import make_task
|
12
|
+
from zrb.util.cli.style import (
|
13
|
+
stylize_bold_yellow,
|
14
|
+
stylize_faint,
|
15
|
+
stylize_section_header,
|
16
|
+
)
|
17
|
+
from zrb.util.group import extract_node_from_args, get_non_empty_subgroups, get_subtasks
|
18
|
+
from zrb.util.load import load_zrb_init
|
19
|
+
from zrb.util.string.conversion import double_quote
|
14
20
|
|
15
21
|
|
16
22
|
class Cli(Group):
|
@@ -25,12 +31,15 @@ class Cli(Group):
|
|
25
31
|
if "h" in kwargs or "help" in kwargs:
|
26
32
|
self._show_task_info(node)
|
27
33
|
return
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
run_kwargs = self._get_run_kwargs(node, args, kwargs)
|
35
|
+
try:
|
36
|
+
result = self._run_task(node, args, run_kwargs)
|
37
|
+
if result is not None:
|
38
|
+
print(result)
|
39
|
+
return result
|
40
|
+
finally:
|
41
|
+
run_command = self._get_run_command(node_path, run_kwargs)
|
42
|
+
self._print_run_command(run_command)
|
34
43
|
|
35
44
|
def _print_run_command(self, run_command: str):
|
36
45
|
print(
|
@@ -39,34 +48,48 @@ class Cli(Group):
|
|
39
48
|
file=sys.stderr,
|
40
49
|
)
|
41
50
|
|
42
|
-
def _get_run_command(
|
43
|
-
self, node_path: list[str], kwargs: dict[str, Any], args: list[str]
|
44
|
-
) -> str:
|
51
|
+
def _get_run_command(self, node_path: list[str], run_kwargs: dict[str, str]) -> str:
|
45
52
|
parts = [self.name] + node_path
|
46
|
-
if len(
|
47
|
-
parts += [
|
48
|
-
|
49
|
-
|
53
|
+
if len(run_kwargs) > 0:
|
54
|
+
parts += [
|
55
|
+
self._get_run_command_param(key, val) for key, val in run_kwargs.items()
|
56
|
+
]
|
50
57
|
return " ".join(parts)
|
51
58
|
|
52
|
-
def
|
59
|
+
def _get_run_command_param(self, key: str, val: str) -> str:
|
60
|
+
if '"' in val or "'" in val or " " in val or val == "":
|
61
|
+
return f"--{key} {double_quote(val)}"
|
62
|
+
return f"--{key} {val}"
|
63
|
+
|
64
|
+
def _run_task(
|
65
|
+
self, task: AnyTask, args: list[str], run_kwargs: dict[str, str]
|
66
|
+
) -> tuple[Any]:
|
67
|
+
shared_ctx = SharedContext(args=args)
|
68
|
+
for task_input in task.inputs:
|
69
|
+
if task_input.name in run_kwargs:
|
70
|
+
task_input.update_shared_context(
|
71
|
+
shared_ctx, run_kwargs[task_input.name]
|
72
|
+
)
|
73
|
+
continue
|
74
|
+
return task.run(Session(shared_ctx=shared_ctx, root_group=self))
|
75
|
+
|
76
|
+
def _get_run_kwargs(
|
77
|
+
self, task: AnyTask, args: list[str], kwargs: dict[str, str]
|
78
|
+
) -> tuple[Any]:
|
53
79
|
arg_index = 0
|
54
|
-
str_kwargs = {key: val for key, val in
|
80
|
+
str_kwargs = {key: val for key, val in kwargs.items()}
|
81
|
+
run_kwargs = {**str_kwargs}
|
55
82
|
shared_ctx = SharedContext(args=args)
|
56
83
|
for task_input in task.inputs:
|
57
84
|
if task_input.name in str_kwargs:
|
58
85
|
continue
|
59
86
|
if arg_index < len(args):
|
60
|
-
|
87
|
+
run_kwargs[task_input.name] = args[arg_index]
|
61
88
|
arg_index += 1
|
62
89
|
continue
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
Session(shared_ctx=shared_ctx, root_group=self), str_kwargs=str_kwargs
|
67
|
-
)
|
68
|
-
except KeyboardInterrupt:
|
69
|
-
pass
|
90
|
+
str_value = task_input.prompt_cli_str(shared_ctx)
|
91
|
+
run_kwargs[task_input.name] = str_value
|
92
|
+
return run_kwargs
|
70
93
|
|
71
94
|
def _show_task_info(self, task: AnyTask):
|
72
95
|
description = task.description
|
@@ -77,8 +100,9 @@ class Cli(Group):
|
|
77
100
|
print()
|
78
101
|
if len(inputs) > 0:
|
79
102
|
print(stylize_section_header("INPUTS"))
|
103
|
+
max_input_name_length = max(len(task_input.name) for task_input in inputs)
|
80
104
|
for task_input in inputs:
|
81
|
-
task_input_name = task_input.name.ljust(
|
105
|
+
task_input_name = task_input.name.ljust(max_input_name_length + 1)
|
82
106
|
print(f" --{task_input_name}: {task_input.description}")
|
83
107
|
print()
|
84
108
|
|
@@ -93,15 +117,17 @@ class Cli(Group):
|
|
93
117
|
subgroups = get_non_empty_subgroups(group)
|
94
118
|
if len(subgroups) > 0:
|
95
119
|
print(stylize_section_header("GROUPS"))
|
120
|
+
max_subgroup_alias_length = max(len(s) for s in subgroups)
|
96
121
|
for alias, subgroup in subgroups.items():
|
97
|
-
alias = alias.ljust(
|
122
|
+
alias = alias.ljust(max_subgroup_alias_length + 1)
|
98
123
|
print(f" {alias}: {subgroup.description}")
|
99
124
|
print()
|
100
125
|
subtasks = get_subtasks(group)
|
101
126
|
if len(subtasks) > 0:
|
102
127
|
print(stylize_section_header("TASKS"))
|
128
|
+
max_subtask_alias_length = max(len(s) for s in subtasks)
|
103
129
|
for alias, subtask in subtasks.items():
|
104
|
-
alias = alias.ljust(
|
130
|
+
alias = alias.ljust(max_subtask_alias_length + 1)
|
105
131
|
print(f" {alias}: {subtask.description}")
|
106
132
|
print()
|
107
133
|
|
@@ -139,14 +165,23 @@ class Cli(Group):
|
|
139
165
|
|
140
166
|
|
141
167
|
cli = Cli(name="zrb", description="Your Automation Powerhouse", banner=BANNER)
|
142
|
-
|
143
|
-
server
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
168
|
+
server_group = cli.add_group(
|
169
|
+
Group(name="server", description="🌐 Server related command")
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
@make_task(
|
174
|
+
name="start-server",
|
175
|
+
description="🚀 Start Zrb Web Server",
|
176
|
+
cli_only=True,
|
177
|
+
retries=0,
|
178
|
+
group=server_group,
|
151
179
|
alias="start",
|
152
180
|
)
|
181
|
+
async def run(_: AnyContext):
|
182
|
+
from uvicorn import Config, Server
|
183
|
+
|
184
|
+
app = create_app(cli, WEB_HTTP_PORT)
|
185
|
+
config = Config(app=app, host="0.0.0.0", port=WEB_HTTP_PORT, loop="asyncio")
|
186
|
+
server = Server(config)
|
187
|
+
await server.serve()
|
zrb/runner/web_app.py
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
import asyncio
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from datetime import datetime, timedelta
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
|
7
|
+
from zrb.config import BANNER, WEB_HTTP_PORT
|
8
|
+
from zrb.context.shared_context import SharedContext
|
9
|
+
from zrb.group.any_group import AnyGroup
|
10
|
+
from zrb.runner.web_controller.group_info_ui.controller import handle_group_info_ui
|
11
|
+
from zrb.runner.web_controller.home_page.controller import handle_home_page
|
12
|
+
from zrb.runner.web_controller.task_ui.controller import handle_task_ui
|
13
|
+
from zrb.runner.web_util import NewSessionResponse
|
14
|
+
from zrb.session.session import Session
|
15
|
+
from zrb.session_state_log.session_state_log import SessionStateLog, SessionStateLogList
|
16
|
+
from zrb.session_state_logger.default_session_state_logger import (
|
17
|
+
default_session_state_logger,
|
18
|
+
)
|
19
|
+
from zrb.task.any_task import AnyTask
|
20
|
+
from zrb.util.group import extract_node_from_args, get_node_path
|
21
|
+
|
22
|
+
|
23
|
+
def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
24
|
+
from contextlib import asynccontextmanager
|
25
|
+
|
26
|
+
from fastapi import FastAPI, HTTPException, Request
|
27
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
28
|
+
from fastapi.staticfiles import StaticFiles
|
29
|
+
|
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()
|
91
|
+
shared_ctx = SharedContext(env=dict(os.environ))
|
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)
|
110
|
+
else:
|
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"
|
124
|
+
)
|
125
|
+
page = int(query_params.get("page", 0))
|
126
|
+
limit = int(query_params.get("limit", 10))
|
127
|
+
try:
|
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,
|
134
|
+
)
|
135
|
+
except Exception as e:
|
136
|
+
raise HTTPException(status_code=500, detail=str(e))
|
137
|
+
|
138
|
+
def read_session(session_name: str) -> SessionStateLog:
|
139
|
+
try:
|
140
|
+
return default_session_state_logger.read(session_name)
|
141
|
+
except Exception as e:
|
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()
|
File without changes
|
File without changes
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from ..any_request_handler import AnyRequestHandler
|
3
|
+
from zrb.group.any_group import AnyGroup
|
4
|
+
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
5
|
+
from zrb.util.string.format import fstring_format
|
7
6
|
|
8
7
|
_DIR = os.path.dirname(__file__)
|
9
8
|
|
@@ -23,9 +22,9 @@ with open(os.path.join(_DIR, "partial", "task_li.html")) as f:
|
|
23
22
|
_TASK_LI_TEMPLATE = f.read()
|
24
23
|
|
25
24
|
|
26
|
-
def handle_group_info_ui(
|
27
|
-
|
28
|
-
|
25
|
+
def handle_group_info_ui(root_group: AnyGroup, group: AnyGroup, url: str):
|
26
|
+
from fastapi.responses import HTMLResponse
|
27
|
+
|
29
28
|
url_parts = url.split("/")
|
30
29
|
parent_url_parts = url_parts[:-2] + [""]
|
31
30
|
parent_url = "/".join(parent_url_parts)
|
@@ -75,7 +74,7 @@ def handle_group_info_ui(
|
|
75
74
|
},
|
76
75
|
)
|
77
76
|
)
|
78
|
-
|
77
|
+
return HTMLResponse(
|
79
78
|
fstring_format(
|
80
79
|
_VIEW_TEMPLATE,
|
81
80
|
{
|
@@ -4,8 +4,8 @@
|
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
6
|
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/pico.min.css">
|
8
|
-
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
|
7
|
+
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
+
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
9
|
<title>Zrb</title>
|
10
10
|
</head>
|
11
11
|
<body>
|
File without changes
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from ..any_request_handler import AnyRequestHandler
|
3
|
+
from zrb.group.any_group import AnyGroup
|
4
|
+
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
5
|
+
from zrb.util.string.format import fstring_format
|
7
6
|
|
8
7
|
_DIR = os.path.dirname(__file__)
|
9
8
|
|
@@ -23,7 +22,9 @@ with open(os.path.join(_DIR, "partial", "task_li.html")) as f:
|
|
23
22
|
_TASK_LI_TEMPLATE = f.read()
|
24
23
|
|
25
24
|
|
26
|
-
def handle_home_page(
|
25
|
+
def handle_home_page(root_group: AnyGroup):
|
26
|
+
from fastapi.responses import HTMLResponse
|
27
|
+
|
27
28
|
subgroups = get_non_empty_subgroups(root_group, web_only=True)
|
28
29
|
group_info = (
|
29
30
|
""
|
@@ -62,7 +63,7 @@ def handle_home_page(handler: AnyRequestHandler, root_group: AnyGroup):
|
|
62
63
|
},
|
63
64
|
)
|
64
65
|
)
|
65
|
-
|
66
|
+
return HTMLResponse(
|
66
67
|
fstring_format(
|
67
68
|
_VIEW_TEMPLATE,
|
68
69
|
{
|