zrb 1.0.0a21__py3-none-any.whl → 1.0.0b3__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 +2 -1
- zrb/__main__.py +3 -3
- zrb/builtin/__init__.py +3 -0
- zrb/builtin/group.py +1 -0
- zrb/builtin/llm/llm_chat.py +5 -3
- zrb/builtin/llm/tool/cli.py +1 -1
- zrb/builtin/llm/tool/rag.py +108 -145
- zrb/builtin/llm/tool/web.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +80 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +150 -42
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service.py +113 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service_factory.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_db_repository.py +0 -10
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_repository.py +37 -16
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/{factory.py → my_entity_repository_factory.py} +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +16 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +57 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +74 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +23 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_api_client.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/{any_client.py → my_module_client.py} +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_client_factory.py +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_direct_client.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +11 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +8 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +47 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app_factory.py +29 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +230 -102
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +236 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{db_engine.py → db_engine_factory.py} +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +12 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/logger_factory.py +10 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/parser_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/app.py +47 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +105 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/user_agent.py +58 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +37 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +37 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +169 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client_factory.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_direct_client.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +160 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +18 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +7 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +117 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service_factory.py +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_db_repository.py +26 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository.py +61 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository_factory.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +89 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +67 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository_factory.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +137 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +179 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +67 -17
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +127 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +43 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +200 -30
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +17 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +78 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +48 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +69 -5
- zrb/builtin/python.py +1 -1
- zrb/builtin/random.py +61 -0
- zrb/cmd/cmd_val.py +6 -5
- zrb/config.py +14 -1
- zrb/content_transformer/any_content_transformer.py +7 -0
- zrb/content_transformer/content_transformer.py +6 -0
- zrb/runner/cli.py +14 -7
- zrb/runner/web_app.py +28 -280
- zrb/runner/web_config/config.py +91 -0
- zrb/runner/web_config/config_factory.py +26 -0
- zrb/runner/web_route/docs_route.py +17 -0
- zrb/runner/web_route/error_page/serve_default_404.py +28 -0
- zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +4 -3
- zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
- zrb/runner/web_route/home_page/home_page_route.py +51 -0
- zrb/runner/web_route/login_api_route.py +31 -0
- zrb/runner/web_route/login_page/login_page_route.py +39 -0
- zrb/runner/web_route/logout_api_route.py +18 -0
- zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
- zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
- zrb/runner/web_route/node_page/node_page_route.py +50 -0
- zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
- zrb/runner/web_route/refresh_token_api_route.py +38 -0
- zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +5 -2
- zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +5 -2
- zrb/runner/web_route/static/static_route.py +44 -0
- zrb/runner/web_route/task_input_api_route.py +47 -0
- zrb/runner/web_route/task_session_api_route.py +147 -0
- zrb/runner/web_schema/session.py +5 -0
- zrb/runner/web_schema/token.py +11 -0
- zrb/runner/web_schema/user.py +32 -0
- zrb/runner/web_util/cookie.py +29 -0
- zrb/runner/{web_util.py → web_util/html.py} +1 -23
- zrb/runner/web_util/token.py +72 -0
- zrb/runner/web_util/user.py +63 -0
- zrb/session/session.py +6 -4
- zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
- zrb/task/base_task.py +56 -8
- zrb/task/base_trigger.py +2 -0
- zrb/task/cmd_task.py +9 -5
- zrb/task/http_check.py +2 -0
- zrb/task/llm_task.py +184 -71
- zrb/task/make_task.py +2 -0
- zrb/task/rsync_task.py +2 -0
- zrb/task/scaffolder.py +8 -5
- zrb/task/scheduler.py +2 -0
- zrb/task/tcp_check.py +2 -0
- zrb/task_status/task_status.py +4 -3
- zrb/util/cmd/command.py +1 -0
- zrb/util/file.py +7 -1
- zrb/util/llm/tool.py +3 -7
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/METADATA +2 -1
- zrb-1.0.0b3.dist-info/RECORD +307 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +0 -27
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_usecase.py +0 -65
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +0 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_usecase.py +0 -245
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/any_client.py +0 -33
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +0 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +0 -53
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
- zrb/runner/web_config.py +0 -274
- zrb/runner/web_controller/home_page/controller.py +0 -33
- zrb/runner/web_controller/login_page/controller.py +0 -25
- zrb/runner/web_controller/logout_page/controller.py +0 -26
- zrb-1.0.0a21.dist-info/RECORD +0 -244
- /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
- /zrb/{runner/web_controller → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission}/__init__.py +0 -0
- /zrb/{runner/web_controller/group_info_page → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role}/__init__.py +0 -0
- /zrb/runner/{web_controller/home_page → web_route}/__init__.py +0 -0
- /zrb/runner/{web_controller/session_page → web_route/home_page}/__init__.py +0 -0
- /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
- /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
- /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
- /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
- /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
- /zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +0 -0
- /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
from pydantic import BaseModel, ConfigDict
|
2
|
+
|
3
|
+
from zrb.group.any_group import AnyGroup
|
4
|
+
from zrb.task.any_task import AnyTask
|
5
|
+
from zrb.util.group import get_all_subtasks
|
6
|
+
|
7
|
+
|
8
|
+
class User(BaseModel):
|
9
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
10
|
+
username: str
|
11
|
+
password: str = ""
|
12
|
+
is_super_admin: bool = False
|
13
|
+
is_guest: bool = False
|
14
|
+
accessible_tasks: list[AnyTask | str] = []
|
15
|
+
|
16
|
+
def is_password_match(self, password: str) -> bool:
|
17
|
+
return self.password == password
|
18
|
+
|
19
|
+
def can_access_group(self, group: AnyGroup) -> bool:
|
20
|
+
if self.is_super_admin:
|
21
|
+
return True
|
22
|
+
all_tasks = get_all_subtasks(group, web_only=True)
|
23
|
+
if any(self.can_access_task(task) for task in all_tasks):
|
24
|
+
return True
|
25
|
+
return False
|
26
|
+
|
27
|
+
def can_access_task(self, task: AnyTask) -> bool:
|
28
|
+
if self.is_super_admin:
|
29
|
+
return True
|
30
|
+
if task.name in self.accessible_tasks or task in self.accessible_tasks:
|
31
|
+
return True
|
32
|
+
return False
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from zrb.runner.web_config.config import WebConfig
|
5
|
+
from zrb.runner.web_schema.token import Token
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
# We want fastapi to only be loaded when necessary to decrease footprint
|
9
|
+
from fastapi import Response
|
10
|
+
|
11
|
+
|
12
|
+
def set_auth_cookie(web_config: WebConfig, response: "Response", token: Token):
|
13
|
+
access_token_max_age = web_config.access_token_expire_minutes * 60
|
14
|
+
refresh_token_max_age = web_config.refresh_token_expire_minutes * 60
|
15
|
+
now = datetime.now(timezone.utc)
|
16
|
+
response.set_cookie(
|
17
|
+
key=web_config.access_token_cookie_name,
|
18
|
+
value=token.access_token,
|
19
|
+
httponly=True,
|
20
|
+
max_age=access_token_max_age,
|
21
|
+
expires=now + timedelta(seconds=access_token_max_age),
|
22
|
+
)
|
23
|
+
response.set_cookie(
|
24
|
+
key=web_config.refresh_token_cookie_name,
|
25
|
+
value=token.refresh_token,
|
26
|
+
httponly=True,
|
27
|
+
max_age=refresh_token_max_age,
|
28
|
+
expires=now + timedelta(seconds=refresh_token_max_age),
|
29
|
+
)
|
@@ -1,23 +1,9 @@
|
|
1
|
-
import os
|
2
|
-
|
3
1
|
from zrb.group.any_group import AnyGroup
|
4
|
-
from zrb.runner.
|
2
|
+
from zrb.runner.web_schema.user import User
|
5
3
|
from zrb.task.any_task import AnyTask
|
6
|
-
from zrb.util.file import read_file
|
7
4
|
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
8
5
|
|
9
6
|
|
10
|
-
def url_to_args(url: str) -> list[str]:
|
11
|
-
stripped_url = url.strip("/")
|
12
|
-
return [part for part in stripped_url.split("/") if part.strip() != ""]
|
13
|
-
|
14
|
-
|
15
|
-
def node_path_to_url(args: list[str]) -> str:
|
16
|
-
pruned_args = [part for part in args if part.strip() != ""]
|
17
|
-
stripped_url = "/".join(pruned_args)
|
18
|
-
return f"/{stripped_url}/"
|
19
|
-
|
20
|
-
|
21
7
|
def get_html_auth_link(user: User) -> str:
|
22
8
|
if user.is_guest and user.is_super_admin:
|
23
9
|
return f"Hi, {user.username}"
|
@@ -26,14 +12,6 @@ def get_html_auth_link(user: User) -> str:
|
|
26
12
|
return f'Hi, {user.username} <a href="/logout">Logout 🚪</a>'
|
27
13
|
|
28
14
|
|
29
|
-
def get_refresh_token_js(refresh_interval_seconds: int):
|
30
|
-
_DIR = os.path.dirname(__file__)
|
31
|
-
return read_file(
|
32
|
-
os.path.join(_DIR, "refresh-token.template.js"),
|
33
|
-
{"refreshIntervalSeconds": f"{refresh_interval_seconds}"},
|
34
|
-
)
|
35
|
-
|
36
|
-
|
37
15
|
def get_html_subtask_info(user: User, parent_url: str, parent_group: AnyGroup) -> str:
|
38
16
|
subtasks = get_subtasks(parent_group, web_only=True)
|
39
17
|
task_li = "\n".join(
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
2
|
+
|
3
|
+
from zrb.runner.web_config.config import WebConfig
|
4
|
+
from zrb.runner.web_schema.token import Token
|
5
|
+
from zrb.runner.web_util.user import get_user_by_credentials
|
6
|
+
|
7
|
+
|
8
|
+
def generate_tokens_by_credentials(
|
9
|
+
web_config: WebConfig, username: str, password: str
|
10
|
+
) -> Token | None:
|
11
|
+
if not web_config.enable_auth:
|
12
|
+
user = web_config.default_user
|
13
|
+
else:
|
14
|
+
user = get_user_by_credentials(web_config, username, password)
|
15
|
+
if user is None:
|
16
|
+
return None
|
17
|
+
access_token = _generate_access_token(web_config, user.username)
|
18
|
+
refresh_token = _generate_refresh_token(web_config, user.username)
|
19
|
+
return Token(
|
20
|
+
access_token=access_token, refresh_token=refresh_token, token_type="bearer"
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
def regenerate_tokens(web_config: WebConfig, refresh_token: str) -> Token:
|
25
|
+
from fastapi import HTTPException
|
26
|
+
from jose import jwt
|
27
|
+
|
28
|
+
# Decode and validate token
|
29
|
+
try:
|
30
|
+
payload = jwt.decode(
|
31
|
+
refresh_token,
|
32
|
+
web_config.secret_key,
|
33
|
+
options={"require_exp": True, "require_sub": True},
|
34
|
+
)
|
35
|
+
except Exception:
|
36
|
+
raise HTTPException(status_code=401, detail="Invalid JWT token")
|
37
|
+
if payload.get("type") != "refresh":
|
38
|
+
raise HTTPException(status_code=401, detail="Invalid token type")
|
39
|
+
username: str = payload.get("sub")
|
40
|
+
if username is None:
|
41
|
+
raise HTTPException(status_code=401, detail="Invalid refresh token")
|
42
|
+
user = web_config.find_user_by_username(username)
|
43
|
+
if user is None:
|
44
|
+
raise HTTPException(status_code=401, detail="User not found")
|
45
|
+
# Create new token
|
46
|
+
new_access_token = _generate_access_token(web_config, username)
|
47
|
+
new_refresh_token = _generate_refresh_token(web_config, username)
|
48
|
+
return Token(
|
49
|
+
access_token=new_access_token,
|
50
|
+
refresh_token=new_refresh_token,
|
51
|
+
token_type="bearer",
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
def _generate_access_token(web_config: WebConfig, username: str) -> str:
|
56
|
+
from jose import jwt
|
57
|
+
|
58
|
+
expire = datetime.now(timezone.utc) + timedelta(
|
59
|
+
minutes=web_config.access_token_expire_minutes
|
60
|
+
)
|
61
|
+
to_encode = {"sub": username, "exp": expire, "type": "access"}
|
62
|
+
return jwt.encode(to_encode, web_config.secret_key)
|
63
|
+
|
64
|
+
|
65
|
+
def _generate_refresh_token(web_config: WebConfig, username: str) -> str:
|
66
|
+
from jose import jwt
|
67
|
+
|
68
|
+
expire = datetime.now(timezone.utc) + timedelta(
|
69
|
+
minutes=web_config.refresh_token_expire_minutes
|
70
|
+
)
|
71
|
+
to_encode = {"sub": username, "exp": expire, "type": "refresh"}
|
72
|
+
return jwt.encode(to_encode, web_config.secret_key)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from zrb.runner.web_config.config import WebConfig
|
4
|
+
from zrb.runner.web_schema.user import User
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
# Import Request only for type checking to reduce runtime dependencies
|
8
|
+
from fastapi import Request
|
9
|
+
|
10
|
+
|
11
|
+
def get_user_by_credentials(
|
12
|
+
web_config: WebConfig, username: str, password: str
|
13
|
+
) -> User | None:
|
14
|
+
user = web_config.find_user_by_username(username)
|
15
|
+
if user is None or not user.is_password_match(password):
|
16
|
+
return None
|
17
|
+
return user
|
18
|
+
|
19
|
+
|
20
|
+
async def get_user_from_request(
|
21
|
+
web_config: WebConfig, request: "Request"
|
22
|
+
) -> User | None:
|
23
|
+
from fastapi.security import OAuth2PasswordBearer
|
24
|
+
|
25
|
+
if not web_config.enable_auth:
|
26
|
+
return web_config.default_user
|
27
|
+
# Normally we use "Depends"
|
28
|
+
get_bearer_token = OAuth2PasswordBearer(tokenUrl="/api/v1/login", auto_error=False)
|
29
|
+
bearer_token = await get_bearer_token(request)
|
30
|
+
token_user = _get_user_from_token(web_config, bearer_token)
|
31
|
+
if token_user is not None:
|
32
|
+
return token_user
|
33
|
+
cookie_user = _get_user_from_cookie(web_config, request)
|
34
|
+
if cookie_user is not None:
|
35
|
+
return cookie_user
|
36
|
+
return web_config.default_user
|
37
|
+
|
38
|
+
|
39
|
+
def _get_user_from_cookie(web_config: WebConfig, request: "Request") -> User | None:
|
40
|
+
token = request.cookies.get(web_config.access_token_cookie_name)
|
41
|
+
if token:
|
42
|
+
return _get_user_from_token(web_config, token)
|
43
|
+
return None
|
44
|
+
|
45
|
+
|
46
|
+
def _get_user_from_token(web_config: WebConfig, token: str) -> User | None:
|
47
|
+
try:
|
48
|
+
from jose import jwt
|
49
|
+
|
50
|
+
payload = jwt.decode(
|
51
|
+
token,
|
52
|
+
web_config.secret_key,
|
53
|
+
options={"require_sub": True, "require_exp": True},
|
54
|
+
)
|
55
|
+
username: str = payload.get("sub")
|
56
|
+
if username is None:
|
57
|
+
return None
|
58
|
+
user = web_config.find_user_by_username(username)
|
59
|
+
if user is None:
|
60
|
+
return None
|
61
|
+
return user
|
62
|
+
except Exception:
|
63
|
+
return None
|
zrb/session/session.py
CHANGED
@@ -11,9 +11,7 @@ from zrb.session_state_log.session_state_log import (
|
|
11
11
|
TaskStatusStateLog,
|
12
12
|
)
|
13
13
|
from zrb.session_state_logger.any_session_state_logger import AnySessionStateLogger
|
14
|
-
from zrb.session_state_logger.
|
15
|
-
default_session_state_logger,
|
16
|
-
)
|
14
|
+
from zrb.session_state_logger.session_state_logger_factory import session_state_logger
|
17
15
|
from zrb.task.any_task import AnyTask
|
18
16
|
from zrb.task_status.task_status import TaskStatus
|
19
17
|
from zrb.util.cli.style import (
|
@@ -126,7 +124,7 @@ class Session(AnySession):
|
|
126
124
|
@property
|
127
125
|
def state_logger(self) -> AnySessionStateLogger:
|
128
126
|
if self._state_logger is None:
|
129
|
-
return
|
127
|
+
return session_state_logger
|
130
128
|
return self._state_logger
|
131
129
|
|
132
130
|
def set_main_task(self, main_task: AnyTask):
|
@@ -215,6 +213,10 @@ class Session(AnySession):
|
|
215
213
|
self._register_single_task(task)
|
216
214
|
for readiness_check in task.readiness_checks:
|
217
215
|
self.register_task(readiness_check)
|
216
|
+
for successor in task.successors:
|
217
|
+
self.register_task(successor)
|
218
|
+
for fallback in task.fallbacks:
|
219
|
+
self.register_task(fallback)
|
218
220
|
for upstream in task.upstreams:
|
219
221
|
self.register_task(upstream)
|
220
222
|
if task not in self._downstreams[upstream]:
|
zrb/task/base_task.py
CHANGED
@@ -242,7 +242,29 @@ class BaseTask(AnyTask):
|
|
242
242
|
def run(
|
243
243
|
self, session: AnySession | None = None, str_kwargs: dict[str, str] = {}
|
244
244
|
) -> Any:
|
245
|
-
return asyncio.run(self.
|
245
|
+
return asyncio.run(self._run_and_cleanup(session, str_kwargs))
|
246
|
+
|
247
|
+
async def _run_and_cleanup(
|
248
|
+
self,
|
249
|
+
session: AnySession | None = None,
|
250
|
+
str_kwargs: dict[str, str] = {},
|
251
|
+
) -> Any:
|
252
|
+
current_task = asyncio.create_task(self.async_run(session, str_kwargs))
|
253
|
+
try:
|
254
|
+
result = await current_task
|
255
|
+
finally:
|
256
|
+
if session and not session.is_terminated:
|
257
|
+
session.terminate()
|
258
|
+
# Cancel all running tasks except the current one
|
259
|
+
pending = [task for task in asyncio.all_tasks() if task is not current_task]
|
260
|
+
for task in pending:
|
261
|
+
task.cancel()
|
262
|
+
if pending:
|
263
|
+
try:
|
264
|
+
await asyncio.wait(pending, timeout=5)
|
265
|
+
except asyncio.CancelledError:
|
266
|
+
pass
|
267
|
+
return result
|
246
268
|
|
247
269
|
async def async_run(
|
248
270
|
self, session: AnySession | None = None, str_kwargs: dict[str, str] = {}
|
@@ -252,7 +274,8 @@ class BaseTask(AnyTask):
|
|
252
274
|
# Update session
|
253
275
|
self.__fill_shared_context_inputs(session.shared_ctx, str_kwargs)
|
254
276
|
self.__fill_shared_context_envs(session.shared_ctx)
|
255
|
-
|
277
|
+
result = await run_async(self.exec_root_tasks(session))
|
278
|
+
return result
|
256
279
|
|
257
280
|
def __fill_shared_context_inputs(
|
258
281
|
self, shared_context: AnySharedContext, str_kwargs: dict[str, str] = {}
|
@@ -265,11 +288,9 @@ class BaseTask(AnyTask):
|
|
265
288
|
def __fill_shared_context_envs(self, shared_context: AnySharedContext):
|
266
289
|
# Inject os environ
|
267
290
|
os_env_map = {
|
268
|
-
key: val
|
269
|
-
for key, val in os.environ.items()
|
270
|
-
if key not in shared_context._env
|
291
|
+
key: val for key, val in os.environ.items() if key not in shared_context.env
|
271
292
|
}
|
272
|
-
shared_context.
|
293
|
+
shared_context.env.update(os_env_map)
|
273
294
|
|
274
295
|
async def exec_root_tasks(self, session: AnySession):
|
275
296
|
session.set_main_task(self)
|
@@ -332,7 +353,7 @@ class BaseTask(AnyTask):
|
|
332
353
|
session.get_task_status(self).mark_as_skipped()
|
333
354
|
return
|
334
355
|
# Wait for task to be ready
|
335
|
-
await run_async(self.__exec_action_until_ready(session))
|
356
|
+
return await run_async(self.__exec_action_until_ready(session))
|
336
357
|
|
337
358
|
def __get_execute_condition(self, session: Session) -> bool:
|
338
359
|
ctx = self.get_ctx(session)
|
@@ -416,6 +437,23 @@ class BaseTask(AnyTask):
|
|
416
437
|
ctx.log_info("Continue monitoring")
|
417
438
|
|
418
439
|
async def __exec_action_and_retry(self, session: AnySession) -> Any:
|
440
|
+
"""
|
441
|
+
Executes an action with retry logic.
|
442
|
+
|
443
|
+
This method attempts to execute the action defined in `_exec_action` with a specified number of retries.
|
444
|
+
If the action fails, it will retry after a specified period until the maximum number of attempts is reached.
|
445
|
+
If the action succeeds, it marks the task as completed and executes any successors.
|
446
|
+
If the action fails permanently, it marks the task as permanently failed and executes any fallbacks.
|
447
|
+
|
448
|
+
Args:
|
449
|
+
session (AnySession): The session object containing the task status and context.
|
450
|
+
|
451
|
+
Returns:
|
452
|
+
Any: The result of the executed action if successful.
|
453
|
+
|
454
|
+
Raises:
|
455
|
+
Exception: If the action fails permanently after all retry attempts.
|
456
|
+
"""
|
419
457
|
ctx = self.get_ctx(session)
|
420
458
|
max_attempt = self._retries + 1
|
421
459
|
ctx.set_max_attempt(max_attempt)
|
@@ -433,13 +471,14 @@ class BaseTask(AnyTask):
|
|
433
471
|
# Put result on xcom
|
434
472
|
task_xcom: Xcom = ctx.xcom.get(self.name)
|
435
473
|
task_xcom.push(result)
|
474
|
+
self.__skip_fallbacks(session)
|
436
475
|
await run_async(self.__exec_successors(session))
|
437
476
|
return result
|
438
477
|
except (asyncio.CancelledError, KeyboardInterrupt):
|
439
478
|
ctx.log_info("Marked as failed")
|
440
479
|
session.get_task_status(self).mark_as_failed()
|
441
480
|
return
|
442
|
-
except
|
481
|
+
except BaseException as e:
|
443
482
|
ctx.log_error(e)
|
444
483
|
if attempt < max_attempt - 1:
|
445
484
|
ctx.log_info("Marked as failed")
|
@@ -447,6 +486,7 @@ class BaseTask(AnyTask):
|
|
447
486
|
continue
|
448
487
|
ctx.log_info("Marked as permanently failed")
|
449
488
|
session.get_task_status(self).mark_as_permanently_failed()
|
489
|
+
self.__skip_successors(session)
|
450
490
|
await run_async(self.__exec_fallbacks(session))
|
451
491
|
raise e
|
452
492
|
|
@@ -457,6 +497,10 @@ class BaseTask(AnyTask):
|
|
457
497
|
]
|
458
498
|
await asyncio.gather(*successor_coros)
|
459
499
|
|
500
|
+
def __skip_successors(self, session: AnySession) -> Any:
|
501
|
+
for successor in self.successors:
|
502
|
+
session.get_task_status(successor).mark_as_skipped()
|
503
|
+
|
460
504
|
async def __exec_fallbacks(self, session: AnySession) -> Any:
|
461
505
|
fallbacks: list[AnyTask] = self.fallbacks
|
462
506
|
fallback_coros = [
|
@@ -464,6 +508,10 @@ class BaseTask(AnyTask):
|
|
464
508
|
]
|
465
509
|
await asyncio.gather(*fallback_coros)
|
466
510
|
|
511
|
+
def __skip_fallbacks(self, session: AnySession) -> Any:
|
512
|
+
for fallback in self.fallbacks:
|
513
|
+
session.get_task_status(fallback).mark_as_skipped()
|
514
|
+
|
467
515
|
async def _exec_action(self, ctx: AnyContext) -> Any:
|
468
516
|
"""Execute the main action of the task.
|
469
517
|
By default will render and run the _action attribute.
|
zrb/task/base_trigger.py
CHANGED
@@ -42,6 +42,7 @@ class BaseTrigger(BaseTask):
|
|
42
42
|
monitor_readiness: bool = False,
|
43
43
|
upstream: list[AnyTask] | AnyTask | None = None,
|
44
44
|
fallback: list[AnyTask] | AnyTask | None = None,
|
45
|
+
successor: list[AnyTask] | AnyTask | None = None,
|
45
46
|
):
|
46
47
|
super().__init__(
|
47
48
|
name=name,
|
@@ -63,6 +64,7 @@ class BaseTrigger(BaseTask):
|
|
63
64
|
monitor_readiness=monitor_readiness,
|
64
65
|
upstream=upstream,
|
65
66
|
fallback=fallback,
|
67
|
+
successor=successor,
|
66
68
|
)
|
67
69
|
self._callbacks = callback
|
68
70
|
self._queue_name = queue_name
|
zrb/task/cmd_task.py
CHANGED
@@ -56,6 +56,7 @@ class CmdTask(BaseTask):
|
|
56
56
|
monitor_readiness: bool = False,
|
57
57
|
upstream: list[AnyTask] | AnyTask | None = None,
|
58
58
|
fallback: list[AnyTask] | AnyTask | None = None,
|
59
|
+
successor: list[AnyTask] | AnyTask | None = None,
|
59
60
|
):
|
60
61
|
super().__init__(
|
61
62
|
name=name,
|
@@ -76,6 +77,7 @@ class CmdTask(BaseTask):
|
|
76
77
|
monitor_readiness=monitor_readiness,
|
77
78
|
upstream=upstream,
|
78
79
|
fallback=fallback,
|
80
|
+
successor=successor,
|
79
81
|
)
|
80
82
|
self._shell = shell
|
81
83
|
self._render_shell = render_shell
|
@@ -233,17 +235,18 @@ class CmdTask(BaseTask):
|
|
233
235
|
|
234
236
|
def _render_cmd_val(self, ctx: AnyContext, cmd_val: CmdVal) -> str:
|
235
237
|
if isinstance(cmd_val, list):
|
238
|
+
cmd_val_list = [
|
239
|
+
self.__render_single_cmd_val(ctx, single_cmd_val)
|
240
|
+
for single_cmd_val in cmd_val
|
241
|
+
]
|
236
242
|
return "\n".join(
|
237
|
-
[
|
238
|
-
self.__render_single_cmd_val(ctx, single_cmd_val)
|
239
|
-
for single_cmd_val in cmd_val
|
240
|
-
]
|
243
|
+
[cmd_val for cmd_val in cmd_val_list if cmd_val is not None]
|
241
244
|
)
|
242
245
|
return self.__render_single_cmd_val(ctx, cmd_val)
|
243
246
|
|
244
247
|
def __render_single_cmd_val(
|
245
248
|
self, ctx: AnyContext, single_cmd_val: SingleCmdVal
|
246
|
-
) -> str:
|
249
|
+
) -> str | None:
|
247
250
|
if callable(single_cmd_val):
|
248
251
|
return single_cmd_val(ctx)
|
249
252
|
if isinstance(single_cmd_val, str):
|
@@ -252,6 +255,7 @@ class CmdTask(BaseTask):
|
|
252
255
|
return single_cmd_val
|
253
256
|
if isinstance(single_cmd_val, AnyCmdVal):
|
254
257
|
return single_cmd_val.to_str(ctx)
|
258
|
+
return None
|
255
259
|
|
256
260
|
def __get_multiline_repr(self, text: str) -> str:
|
257
261
|
lines_repr: list[str] = []
|
zrb/task/http_check.py
CHANGED
@@ -28,6 +28,7 @@ class HttpCheck(BaseTask):
|
|
28
28
|
execute_condition: bool | str | Callable[[Context], bool] = True,
|
29
29
|
upstream: list[AnyTask] | AnyTask | None = None,
|
30
30
|
fallback: list[AnyTask] | AnyTask | None = None,
|
31
|
+
successor: list[AnyTask] | AnyTask | None = None,
|
31
32
|
):
|
32
33
|
super().__init__(
|
33
34
|
name=name,
|
@@ -41,6 +42,7 @@ class HttpCheck(BaseTask):
|
|
41
42
|
retries=0,
|
42
43
|
upstream=upstream,
|
43
44
|
fallback=fallback,
|
45
|
+
successor=successor,
|
44
46
|
)
|
45
47
|
self._url = url
|
46
48
|
self._render_url = render_url
|