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.
Files changed (195) hide show
  1. zrb/__init__.py +2 -1
  2. zrb/__main__.py +3 -3
  3. zrb/builtin/__init__.py +3 -0
  4. zrb/builtin/group.py +1 -0
  5. zrb/builtin/llm/llm_chat.py +5 -3
  6. zrb/builtin/llm/tool/cli.py +1 -1
  7. zrb/builtin/llm/tool/rag.py +108 -145
  8. zrb/builtin/llm/tool/web.py +1 -1
  9. zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -2
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +80 -20
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +150 -42
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +16 -6
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +57 -0
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +74 -0
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +23 -0
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
  26. 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
  27. 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
  28. 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
  29. 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
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +11 -11
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +2 -2
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +8 -8
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +47 -20
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app_factory.py +29 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +230 -102
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +236 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{db_engine.py → db_engine_factory.py} +1 -1
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +12 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/logger_factory.py +10 -0
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/parser_factory.py +7 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/app.py +47 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +105 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/user_agent.py +58 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +37 -0
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +37 -1
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +1 -1
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py +16 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +169 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client_factory.py +9 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_direct_client.py +15 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +160 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +18 -1
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +7 -3
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +117 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service_factory.py +11 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_db_repository.py +26 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository.py +61 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository_factory.py +13 -0
  59. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +89 -0
  60. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +67 -0
  61. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository_factory.py +13 -0
  62. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +137 -0
  63. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service_factory.py +7 -0
  64. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +179 -12
  65. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +67 -17
  66. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +2 -2
  67. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +127 -0
  68. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +7 -0
  69. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +43 -14
  70. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +200 -30
  71. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
  72. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
  73. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
  74. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
  75. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
  76. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
  77. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
  78. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
  79. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
  80. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
  81. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
  82. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
  83. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
  84. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
  85. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
  86. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
  87. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
  88. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
  89. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
  90. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
  91. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
  92. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
  93. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
  94. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
  95. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
  96. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
  97. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
  98. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
  99. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +17 -5
  100. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +78 -4
  101. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +48 -0
  102. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +69 -5
  103. zrb/builtin/python.py +1 -1
  104. zrb/builtin/random.py +61 -0
  105. zrb/cmd/cmd_val.py +6 -5
  106. zrb/config.py +14 -1
  107. zrb/content_transformer/any_content_transformer.py +7 -0
  108. zrb/content_transformer/content_transformer.py +6 -0
  109. zrb/runner/cli.py +14 -7
  110. zrb/runner/web_app.py +28 -280
  111. zrb/runner/web_config/config.py +91 -0
  112. zrb/runner/web_config/config_factory.py +26 -0
  113. zrb/runner/web_route/docs_route.py +17 -0
  114. zrb/runner/web_route/error_page/serve_default_404.py +28 -0
  115. zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +4 -3
  116. zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
  117. zrb/runner/web_route/home_page/home_page_route.py +51 -0
  118. zrb/runner/web_route/login_api_route.py +31 -0
  119. zrb/runner/web_route/login_page/login_page_route.py +39 -0
  120. zrb/runner/web_route/logout_api_route.py +18 -0
  121. zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
  122. zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
  123. zrb/runner/web_route/node_page/node_page_route.py +50 -0
  124. zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
  125. zrb/runner/web_route/refresh_token_api_route.py +38 -0
  126. zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +5 -2
  127. zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +5 -2
  128. zrb/runner/web_route/static/static_route.py +44 -0
  129. zrb/runner/web_route/task_input_api_route.py +47 -0
  130. zrb/runner/web_route/task_session_api_route.py +147 -0
  131. zrb/runner/web_schema/session.py +5 -0
  132. zrb/runner/web_schema/token.py +11 -0
  133. zrb/runner/web_schema/user.py +32 -0
  134. zrb/runner/web_util/cookie.py +29 -0
  135. zrb/runner/{web_util.py → web_util/html.py} +1 -23
  136. zrb/runner/web_util/token.py +72 -0
  137. zrb/runner/web_util/user.py +63 -0
  138. zrb/session/session.py +6 -4
  139. zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
  140. zrb/task/base_task.py +56 -8
  141. zrb/task/base_trigger.py +2 -0
  142. zrb/task/cmd_task.py +9 -5
  143. zrb/task/http_check.py +2 -0
  144. zrb/task/llm_task.py +184 -71
  145. zrb/task/make_task.py +2 -0
  146. zrb/task/rsync_task.py +2 -0
  147. zrb/task/scaffolder.py +8 -5
  148. zrb/task/scheduler.py +2 -0
  149. zrb/task/tcp_check.py +2 -0
  150. zrb/task_status/task_status.py +4 -3
  151. zrb/util/cmd/command.py +1 -0
  152. zrb/util/file.py +7 -1
  153. zrb/util/llm/tool.py +3 -7
  154. {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/METADATA +2 -1
  155. zrb-1.0.0b3.dist-info/RECORD +307 -0
  156. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +0 -27
  157. 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
  158. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +0 -6
  159. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +0 -6
  160. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +0 -9
  161. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +0 -20
  162. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_usecase.py +0 -245
  163. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/any_client.py +0 -33
  164. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +0 -7
  165. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +0 -6
  166. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +0 -9
  167. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
  168. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +0 -53
  169. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
  170. zrb/runner/web_config.py +0 -274
  171. zrb/runner/web_controller/home_page/controller.py +0 -33
  172. zrb/runner/web_controller/login_page/controller.py +0 -25
  173. zrb/runner/web_controller/logout_page/controller.py +0 -26
  174. zrb-1.0.0a21.dist-info/RECORD +0 -244
  175. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
  176. /zrb/{runner/web_controller → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission}/__init__.py +0 -0
  177. /zrb/{runner/web_controller/group_info_page → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role}/__init__.py +0 -0
  178. /zrb/runner/{web_controller/home_page → web_route}/__init__.py +0 -0
  179. /zrb/runner/{web_controller/session_page → web_route/home_page}/__init__.py +0 -0
  180. /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
  181. /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
  182. /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
  183. /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
  184. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
  185. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +0 -0
  186. /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
  187. /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
  188. /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
  189. /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
  190. /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
  191. /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
  192. /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
  193. /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
  194. {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/WHEEL +0 -0
  195. {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.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)
@@ -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.default_session_state_logger import (
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 default_session_state_logger
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]:
@@ -1,4 +1,4 @@
1
1
  from zrb.config import SESSION_LOG_DIR
2
2
  from zrb.session_state_logger.file_session_state_logger import FileSessionStateLogger
3
3
 
4
- default_session_state_logger = FileSessionStateLogger(SESSION_LOG_DIR)
4
+ session_state_logger = FileSessionStateLogger(SESSION_LOG_DIR)
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.async_run(session, str_kwargs))
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
- return await run_async(self.exec_root_tasks(session))
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._env.update(os_env_map)
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 Exception as e:
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