zrb 1.0.0a21__py3-none-any.whl → 1.0.0b2__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.
Files changed (191) hide show
  1. zrb/__init__.py +2 -1
  2. zrb/__main__.py +0 -3
  3. zrb/builtin/__init__.py +3 -0
  4. zrb/builtin/group.py +1 -0
  5. zrb/builtin/llm/llm_chat.py +2 -2
  6. zrb/builtin/llm/tool/web.py +1 -1
  7. zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -2
  9. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +80 -20
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +150 -42
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +16 -6
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +57 -0
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +72 -0
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +23 -0
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
  24. 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
  25. 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
  26. 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
  27. 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
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +11 -11
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +2 -2
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +8 -8
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +47 -20
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app_factory.py +29 -0
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +185 -101
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +236 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{db_engine.py → db_engine_factory.py} +1 -1
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +12 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/logger_factory.py +10 -0
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/parser_factory.py +7 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/app.py +47 -0
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +105 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/user_agent.py +58 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +37 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +25 -1
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +1 -1
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py +16 -0
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +163 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client_factory.py +9 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_direct_client.py +15 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +160 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +18 -1
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +7 -3
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +117 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service_factory.py +11 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_db_repository.py +26 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository.py +61 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository_factory.py +13 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +75 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +59 -0
  59. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository_factory.py +13 -0
  60. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +105 -0
  61. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service_factory.py +7 -0
  62. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +42 -13
  63. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +38 -17
  64. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +2 -2
  65. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +105 -0
  66. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +7 -0
  67. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +43 -14
  68. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +198 -28
  69. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
  70. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
  71. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
  72. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
  73. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
  74. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
  75. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
  76. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
  77. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
  78. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
  79. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
  80. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
  81. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
  82. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
  83. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
  84. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
  85. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
  86. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
  87. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
  88. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
  89. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
  90. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
  91. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
  92. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
  93. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
  94. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
  95. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
  96. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
  97. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +17 -5
  98. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +50 -4
  99. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +52 -0
  100. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +30 -5
  101. zrb/builtin/python.py +1 -1
  102. zrb/builtin/random.py +61 -0
  103. zrb/cmd/cmd_val.py +6 -5
  104. zrb/content_transformer/any_content_transformer.py +7 -0
  105. zrb/content_transformer/content_transformer.py +6 -0
  106. zrb/runner/cli.py +14 -7
  107. zrb/runner/web_app.py +28 -280
  108. zrb/runner/web_config/config.py +91 -0
  109. zrb/runner/web_config/config_factory.py +26 -0
  110. zrb/runner/web_route/docs_route.py +17 -0
  111. zrb/runner/web_route/error_page/serve_default_404.py +28 -0
  112. zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +2 -2
  113. zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
  114. zrb/runner/web_route/home_page/home_page_route.py +51 -0
  115. zrb/runner/web_route/login_api_route.py +31 -0
  116. zrb/runner/web_route/login_page/login_page_route.py +39 -0
  117. zrb/runner/web_route/logout_api_route.py +18 -0
  118. zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
  119. zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
  120. zrb/runner/web_route/node_page/node_page_route.py +50 -0
  121. zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
  122. zrb/runner/web_route/refresh_token_api_route.py +38 -0
  123. zrb/runner/web_route/static/static_route.py +44 -0
  124. zrb/runner/web_route/task_input_api_route.py +47 -0
  125. zrb/runner/web_route/task_session_api_route.py +102 -0
  126. zrb/runner/web_schema/session.py +5 -0
  127. zrb/runner/web_schema/token.py +11 -0
  128. zrb/runner/web_schema/user.py +32 -0
  129. zrb/runner/web_util/cookie.py +29 -0
  130. zrb/runner/{web_util.py → web_util/html.py} +1 -23
  131. zrb/runner/web_util/token.py +72 -0
  132. zrb/runner/web_util/user.py +63 -0
  133. zrb/session/session.py +6 -4
  134. zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
  135. zrb/task/base_task.py +53 -6
  136. zrb/task/base_trigger.py +2 -0
  137. zrb/task/cmd_task.py +9 -5
  138. zrb/task/http_check.py +2 -0
  139. zrb/task/llm_task.py +2 -0
  140. zrb/task/make_task.py +2 -0
  141. zrb/task/rsync_task.py +2 -0
  142. zrb/task/scaffolder.py +8 -5
  143. zrb/task/scheduler.py +2 -0
  144. zrb/task/tcp_check.py +2 -0
  145. zrb/task_status/task_status.py +4 -3
  146. zrb/util/cmd/command.py +1 -0
  147. zrb/util/file.py +7 -1
  148. {zrb-1.0.0a21.dist-info → zrb-1.0.0b2.dist-info}/METADATA +1 -1
  149. zrb-1.0.0b2.dist-info/RECORD +307 -0
  150. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +0 -27
  151. 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
  152. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +0 -6
  153. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +0 -6
  154. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +0 -9
  155. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +0 -20
  156. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_usecase.py +0 -245
  157. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/any_client.py +0 -33
  158. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +0 -7
  159. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +0 -6
  160. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +0 -9
  161. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
  162. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +0 -53
  163. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
  164. zrb/runner/web_config.py +0 -274
  165. zrb/runner/web_controller/home_page/controller.py +0 -33
  166. zrb/runner/web_controller/login_page/controller.py +0 -25
  167. zrb/runner/web_controller/logout_page/controller.py +0 -26
  168. zrb-1.0.0a21.dist-info/RECORD +0 -244
  169. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
  170. /zrb/{runner/web_controller → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission}/__init__.py +0 -0
  171. /zrb/{runner/web_controller/group_info_page → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role}/__init__.py +0 -0
  172. /zrb/runner/{web_controller/home_page → web_route}/__init__.py +0 -0
  173. /zrb/runner/{web_controller/session_page → web_route/home_page}/__init__.py +0 -0
  174. /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
  175. /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
  176. /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
  177. /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
  178. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
  179. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +0 -0
  180. /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
  181. /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
  182. /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
  183. /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
  184. /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
  185. /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
  186. /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
  187. /zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +0 -0
  188. /zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +0 -0
  189. /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
  190. {zrb-1.0.0a21.dist-info → zrb-1.0.0b2.dist-info}/WHEEL +0 -0
  191. {zrb-1.0.0a21.dist-info → zrb-1.0.0b2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,39 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.web_config.config import WebConfig
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.util.file import read_file
9
+ from zrb.util.string.format import fstring_format
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_login_page(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Request
22
+ from fastapi.responses import HTMLResponse
23
+
24
+ @app.get("/login", response_class=HTMLResponse, include_in_schema=False)
25
+ async def login(request: Request) -> HTMLResponse:
26
+ _DIR = os.path.dirname(__file__)
27
+ _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
+ user = await get_user_from_request(web_config, request)
29
+ auth_link = get_html_auth_link(user)
30
+ return HTMLResponse(
31
+ fstring_format(
32
+ _VIEW_TEMPLATE,
33
+ {
34
+ "name": root_group.name,
35
+ "description": root_group.description,
36
+ "auth_link": auth_link,
37
+ },
38
+ )
39
+ )
@@ -0,0 +1,18 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from zrb.runner.web_config.config import WebConfig
4
+
5
+ if TYPE_CHECKING:
6
+ # We want fastapi to only be loaded when necessary to decrease footprint
7
+ from fastapi import FastAPI
8
+
9
+
10
+ def serve_logout_api(app: "FastAPI", web_config: WebConfig) -> None:
11
+ from fastapi import Response
12
+
13
+ @app.get("/api/v1/logout")
14
+ @app.post("/api/v1/logout")
15
+ async def logout_api(response: Response):
16
+ response.delete_cookie(web_config.access_token_cookie_name)
17
+ response.delete_cookie(web_config.refresh_token_cookie_name)
18
+ return {"message": "Logout successful"}
@@ -0,0 +1,40 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.web_config.config import WebConfig
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.util.file import read_file
9
+ from zrb.util.string.format import fstring_format
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_logout_page(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Request
22
+ from fastapi.responses import HTMLResponse
23
+
24
+ @app.get("/logout", response_class=HTMLResponse, include_in_schema=False)
25
+ async def logout(request: Request) -> HTMLResponse:
26
+ _DIR = os.path.dirname(__file__)
27
+ _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
+ user = await get_user_from_request(web_config, request)
29
+ auth_link = get_html_auth_link(user)
30
+ return HTMLResponse(
31
+ fstring_format(
32
+ _VIEW_TEMPLATE,
33
+ {
34
+ "name": root_group.name,
35
+ "description": root_group.description,
36
+ "auth_link": auth_link,
37
+ "user": user,
38
+ },
39
+ )
40
+ )
@@ -1,8 +1,8 @@
1
1
  import os
2
2
 
3
3
  from zrb.group.any_group import AnyGroup
4
- from zrb.runner.web_config import User
5
- from zrb.runner.web_util import (
4
+ from zrb.runner.web_schema.user import User
5
+ from zrb.runner.web_util.html import (
6
6
  get_html_auth_link,
7
7
  get_html_subgroup_info,
8
8
  get_html_subtask_info,
@@ -11,7 +11,7 @@ from zrb.util.file import read_file
11
11
  from zrb.util.string.format import fstring_format
12
12
 
13
13
 
14
- def show_group_info_page(user: User, root_group: AnyGroup, group: AnyGroup, url: str):
14
+ def show_group_page(user: User, root_group: AnyGroup, group: AnyGroup, url: str):
15
15
  from fastapi.responses import HTMLResponse
16
16
 
17
17
  _DIR = os.path.dirname(__file__)
@@ -0,0 +1,50 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.context.shared_context import SharedContext
5
+ from zrb.group.any_group import AnyGroup
6
+ from zrb.runner.web_config.config import WebConfig
7
+ from zrb.runner.web_route.error_page.show_error_page import show_error_page
8
+ from zrb.runner.web_route.node_page.group.show_group_page import show_group_page
9
+ from zrb.runner.web_route.node_page.task.show_task_page import show_task_page
10
+ from zrb.runner.web_util.user import get_user_from_request
11
+ from zrb.session.session import Session
12
+ from zrb.task.any_task import AnyTask
13
+ from zrb.util.group import NodeNotFoundError, extract_node_from_args
14
+
15
+ if TYPE_CHECKING:
16
+ # We want fastapi to only be loaded when necessary to decrease footprint
17
+ from fastapi import FastAPI
18
+
19
+
20
+ def serve_node_page(
21
+ app: "FastAPI",
22
+ root_group: AnyGroup,
23
+ web_config: WebConfig,
24
+ ) -> None:
25
+ from fastapi import Request
26
+ from fastapi.responses import HTMLResponse
27
+
28
+ @app.get("/ui/{path:path}", response_class=HTMLResponse, include_in_schema=False)
29
+ async def ui_page(path: str, request: Request) -> HTMLResponse:
30
+ user = await get_user_from_request(web_config, request)
31
+ # Avoid capturing '/ui' itself
32
+ if not path:
33
+ return show_error_page(user, root_group, 422, "Undefined path")
34
+ args = path.strip("/").split("/")
35
+ try:
36
+ node, node_path, residual_args = extract_node_from_args(root_group, args)
37
+ except NodeNotFoundError as e:
38
+ return show_error_page(user, root_group, 404, str(e))
39
+ url = f"/ui/{'/'.join(node_path)}/"
40
+ if isinstance(node, AnyTask):
41
+ if not user.can_access_task(node):
42
+ return show_error_page(user, root_group, 403, "Forbidden")
43
+ shared_ctx = SharedContext(env=dict(os.environ))
44
+ session = Session(shared_ctx=shared_ctx, root_group=root_group)
45
+ return show_task_page(user, root_group, node, session, url, residual_args)
46
+ elif isinstance(node, AnyGroup):
47
+ if not user.can_access_group(node):
48
+ return show_error_page(user, root_group, 403, "Forbidden")
49
+ return show_group_page(user, root_group, node, url)
50
+ return show_error_page(user, root_group, 404, "Not found")
@@ -2,15 +2,15 @@ import json
2
2
  import os
3
3
 
4
4
  from zrb.group.any_group import AnyGroup
5
- from zrb.runner.web_config import User
6
- from zrb.runner.web_util import get_html_auth_link
5
+ from zrb.runner.web_schema.user import User
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
7
  from zrb.session.any_session import AnySession
8
8
  from zrb.task.any_task import AnyTask
9
9
  from zrb.util.file import read_file
10
10
  from zrb.util.string.format import fstring_format
11
11
 
12
12
 
13
- def show_session_page(
13
+ def show_task_page(
14
14
  user: User,
15
15
  root_group: AnyGroup,
16
16
  task: AnyTask,
@@ -0,0 +1,38 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from zrb.runner.web_config.config import WebConfig
4
+ from zrb.runner.web_schema.token import RefreshTokenRequest
5
+ from zrb.runner.web_util.cookie import set_auth_cookie
6
+ from zrb.runner.web_util.token import regenerate_tokens
7
+
8
+ if TYPE_CHECKING:
9
+ # We want fastapi to only be loaded when necessary to decrease footprint
10
+ from fastapi import FastAPI
11
+
12
+
13
+ def serve_refresh_token_api(app: "FastAPI", web_config: WebConfig) -> None:
14
+ from fastapi import Cookie, Response
15
+ from fastapi.responses import JSONResponse
16
+
17
+ @app.post("/api/v1/refresh-token")
18
+ async def refresh_token_api(
19
+ response: Response,
20
+ body: RefreshTokenRequest = None,
21
+ refresh_token_cookie: str = Cookie(
22
+ None, alias=web_config.refresh_token_cookie_name
23
+ ),
24
+ ):
25
+ # Try to get the refresh token from the request body first
26
+ refresh_token = body.refresh_token if body else None
27
+ # If not in the body, try to get it from the cookie
28
+ if not refresh_token:
29
+ refresh_token = refresh_token_cookie
30
+ # If we still don't have a refresh token, raise an exception
31
+ if not refresh_token:
32
+ return JSONResponse(
33
+ content={"detail": "Refresh token not provided"}, status_code=400
34
+ )
35
+ # Get token
36
+ new_token = regenerate_tokens(web_config, refresh_token)
37
+ set_auth_cookie(web_config, response, new_token)
38
+ return new_token
@@ -0,0 +1,44 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.runner.web_config.config import WebConfig
5
+ from zrb.util.file import read_file
6
+
7
+ if TYPE_CHECKING:
8
+ # We want fastapi to only be loaded when necessary to decrease footprint
9
+ from fastapi import FastAPI
10
+
11
+
12
+ def serve_static_resources(app: "FastAPI", web_config: WebConfig) -> None:
13
+ from fastapi import HTTPException
14
+ from fastapi.responses import FileResponse, PlainTextResponse
15
+ from fastapi.staticfiles import StaticFiles
16
+
17
+ _STATIC_DIR = os.path.join(os.path.dirname(__file__), "resources")
18
+
19
+ app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
20
+
21
+ # Serve static files
22
+ @app.get("/static/{file_path:path}", include_in_schema=False)
23
+ async def static_files(file_path: str):
24
+ full_path = os.path.join(_STATIC_DIR, file_path)
25
+ if os.path.isfile(full_path):
26
+ return FileResponse(full_path)
27
+ raise HTTPException(status_code=404, detail="File not found")
28
+
29
+ @app.get("/refresh-token.js", include_in_schema=False)
30
+ async def refresh_token_js():
31
+ return PlainTextResponse(
32
+ content=_get_refresh_token_js(
33
+ 60 * web_config.refresh_token_expire_minutes / 3
34
+ ),
35
+ media_type="application/javascript",
36
+ )
37
+
38
+
39
+ def _get_refresh_token_js(refresh_interval_seconds: int):
40
+ _DIR = os.path.dirname(__file__)
41
+ return read_file(
42
+ os.path.join(_DIR, "refresh-token.template.js"),
43
+ {"refreshIntervalSeconds": f"{refresh_interval_seconds}"},
44
+ )
@@ -0,0 +1,47 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.common_util import get_run_kwargs
6
+ from zrb.runner.web_config.config import WebConfig
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.task.any_task import AnyTask
9
+ from zrb.util.group import NodeNotFoundError, extract_node_from_args
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_task_input_api(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Query, Request
22
+ from fastapi.responses import JSONResponse
23
+
24
+ @app.get("/api/v1/task-inputs/{path:path}", response_model=dict[str, str])
25
+ async def get_default_inputs_api(
26
+ path: str,
27
+ request: Request,
28
+ query: str = Query("{}", description="JSON encoded inputs"),
29
+ ) -> dict[str, str]:
30
+ """
31
+ Getting input completion for path
32
+ """
33
+ user = await get_user_from_request(web_config, request)
34
+ args = path.strip("/").split("/")
35
+ try:
36
+ task, _, _ = extract_node_from_args(root_group, args)
37
+ except NodeNotFoundError:
38
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
39
+ if isinstance(task, AnyTask):
40
+ if not user.can_access_task(task):
41
+ return JSONResponse(content={"detail": "Forbidden"}, status_code=403)
42
+ query_dict = json.loads(query)
43
+ run_kwargs = get_run_kwargs(
44
+ task=task, args=[], kwargs=query_dict, prompt=False
45
+ )
46
+ return run_kwargs
47
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import os
3
+ from datetime import datetime, timedelta
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from zrb.context.shared_context import SharedContext
7
+ from zrb.group.any_group import AnyGroup
8
+ from zrb.runner.web_config.config import WebConfig
9
+ from zrb.runner.web_schema.session import NewSessionResponse
10
+ from zrb.runner.web_util.user import get_user_from_request
11
+ from zrb.session.session import Session
12
+ from zrb.session_state_log.session_state_log import SessionStateLog, SessionStateLogList
13
+ from zrb.session_state_logger.any_session_state_logger import AnySessionStateLogger
14
+ from zrb.task.any_task import AnyTask
15
+ from zrb.util.group import NodeNotFoundError, extract_node_from_args, get_node_path
16
+
17
+ if TYPE_CHECKING:
18
+ # We want fastapi to only be loaded when necessary to decrease footprint
19
+
20
+ from fastapi import FastAPI
21
+
22
+
23
+ def serve_task_session_api(
24
+ app: "FastAPI",
25
+ root_group: AnyGroup,
26
+ web_config: WebConfig,
27
+ session_state_logger: AnySessionStateLogger,
28
+ coroutines: list,
29
+ ) -> None:
30
+ from fastapi import Query, Request
31
+ from fastapi.responses import JSONResponse
32
+
33
+ @app.post("/api/v1/task-sessions/{path:path}")
34
+ async def create_new_task_session_api(
35
+ path: str,
36
+ request: Request,
37
+ inputs: dict[str, Any],
38
+ ) -> NewSessionResponse:
39
+ """
40
+ Creating new session
41
+ """
42
+ user = await get_user_from_request(web_config, request)
43
+ args = path.strip("/").split("/")
44
+ try:
45
+ task, _, residual_args = extract_node_from_args(root_group, args)
46
+ except NodeNotFoundError:
47
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
48
+ if isinstance(task, AnyTask):
49
+ if not user.can_access_task(task):
50
+ return JSONResponse(content={"detail": "Forbidden"}, status_code=403)
51
+ session_name = residual_args[0] if residual_args else None
52
+ if not session_name:
53
+ shared_ctx = SharedContext(env=dict(os.environ))
54
+ session = Session(shared_ctx=shared_ctx, root_group=root_group)
55
+ coro = asyncio.create_task(task.async_run(session, str_kwargs=inputs))
56
+ coroutines.append(coro)
57
+ coro.add_done_callback(lambda coro: coroutines.remove(coro))
58
+ return NewSessionResponse(session_name=session.name)
59
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
60
+
61
+ @app.get(
62
+ "/api/v1/task-sessions/{path:path}",
63
+ response_model=SessionStateLog | SessionStateLogList,
64
+ )
65
+ async def get_task_session_api(
66
+ path: str,
67
+ request: Request,
68
+ min_start_query: str = Query(default=None, alias="from"),
69
+ max_start_query: str = Query(default=None, alias="to"),
70
+ page: int = Query(default=0, alias="page"),
71
+ limit: int = Query(default=10, alias="limit"),
72
+ ) -> SessionStateLog | SessionStateLogList:
73
+ """
74
+ Getting existing session or sessions
75
+ """
76
+ user = await get_user_from_request(web_config, request)
77
+ args = path.strip("/").split("/")
78
+ try:
79
+ task, _, residual_args = extract_node_from_args(root_group, args)
80
+ except NodeNotFoundError:
81
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
82
+ if isinstance(task, AnyTask) and residual_args:
83
+ if not user.can_access_task(task):
84
+ return JSONResponse(content={"detail": "Forbidden"}, status_code=403)
85
+ if residual_args[0] == "list":
86
+ task_path = get_node_path(root_group, task)
87
+ max_start_time = (
88
+ datetime.now()
89
+ if max_start_query is None
90
+ else datetime.strptime(max_start_query, "%Y-%m-%d %H:%M:%S")
91
+ )
92
+ min_start_time = (
93
+ max_start_time - timedelta(hours=1)
94
+ if min_start_query is None
95
+ else datetime.strptime(min_start_query, "%Y-%m-%d %H:%M:%S")
96
+ )
97
+ return session_state_logger.list(
98
+ task_path, min_start_time, max_start_time, page, limit
99
+ )
100
+ else:
101
+ return session_state_logger.read(residual_args[0])
102
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class NewSessionResponse(BaseModel):
5
+ session_name: str
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class RefreshTokenRequest(BaseModel):
5
+ refresh_token: str
6
+
7
+
8
+ class Token(BaseModel):
9
+ access_token: str
10
+ refresh_token: str
11
+ token_type: str
@@ -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.web_config import User
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)