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,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
+ loop = asyncio.new_event_loop()
246
+ try:
247
+ return loop.run_until_complete(self._run_and_cleanup(session, str_kwargs))
248
+ finally:
249
+ loop.close()
250
+
251
+ async def _run_and_cleanup(
252
+ self, session: AnySession | None = None, str_kwargs: dict[str, str] = {}
253
+ ) -> Any:
254
+ try:
255
+ result = await self.async_run(session, str_kwargs)
256
+ finally:
257
+ if not session.is_terminated:
258
+ session.terminate()
259
+ # Cancel all running tasks except the current one
260
+ current_task = asyncio.current_task()
261
+ pending = [task for task in asyncio.all_tasks() if task is not current_task]
262
+ for task in pending:
263
+ task.cancel()
264
+ # Wait for all tasks to complete with a timeout
265
+ if pending:
266
+ await asyncio.wait(pending, timeout=5)
267
+ return result
246
268
 
247
269
  async def async_run(
248
270
  self, session: AnySession | None = None, str_kwargs: dict[str, str] = {}
@@ -265,11 +287,9 @@ class BaseTask(AnyTask):
265
287
  def __fill_shared_context_envs(self, shared_context: AnySharedContext):
266
288
  # Inject os environ
267
289
  os_env_map = {
268
- key: val
269
- for key, val in os.environ.items()
270
- if key not in shared_context._env
290
+ key: val for key, val in os.environ.items() if key not in shared_context.env
271
291
  }
272
- shared_context._env.update(os_env_map)
292
+ shared_context.env.update(os_env_map)
273
293
 
274
294
  async def exec_root_tasks(self, session: AnySession):
275
295
  session.set_main_task(self)
@@ -416,6 +436,23 @@ class BaseTask(AnyTask):
416
436
  ctx.log_info("Continue monitoring")
417
437
 
418
438
  async def __exec_action_and_retry(self, session: AnySession) -> Any:
439
+ """
440
+ Executes an action with retry logic.
441
+
442
+ This method attempts to execute the action defined in `_exec_action` with a specified number of retries.
443
+ If the action fails, it will retry after a specified period until the maximum number of attempts is reached.
444
+ If the action succeeds, it marks the task as completed and executes any successors.
445
+ If the action fails permanently, it marks the task as permanently failed and executes any fallbacks.
446
+
447
+ Args:
448
+ session (AnySession): The session object containing the task status and context.
449
+
450
+ Returns:
451
+ Any: The result of the executed action if successful.
452
+
453
+ Raises:
454
+ Exception: If the action fails permanently after all retry attempts.
455
+ """
419
456
  ctx = self.get_ctx(session)
420
457
  max_attempt = self._retries + 1
421
458
  ctx.set_max_attempt(max_attempt)
@@ -433,13 +470,14 @@ class BaseTask(AnyTask):
433
470
  # Put result on xcom
434
471
  task_xcom: Xcom = ctx.xcom.get(self.name)
435
472
  task_xcom.push(result)
473
+ self.__skip_fallbacks(session)
436
474
  await run_async(self.__exec_successors(session))
437
475
  return result
438
476
  except (asyncio.CancelledError, KeyboardInterrupt):
439
477
  ctx.log_info("Marked as failed")
440
478
  session.get_task_status(self).mark_as_failed()
441
479
  return
442
- except Exception as e:
480
+ except BaseException as e:
443
481
  ctx.log_error(e)
444
482
  if attempt < max_attempt - 1:
445
483
  ctx.log_info("Marked as failed")
@@ -447,6 +485,7 @@ class BaseTask(AnyTask):
447
485
  continue
448
486
  ctx.log_info("Marked as permanently failed")
449
487
  session.get_task_status(self).mark_as_permanently_failed()
488
+ self.__skip_successors(session)
450
489
  await run_async(self.__exec_fallbacks(session))
451
490
  raise e
452
491
 
@@ -457,6 +496,10 @@ class BaseTask(AnyTask):
457
496
  ]
458
497
  await asyncio.gather(*successor_coros)
459
498
 
499
+ def __skip_successors(self, session: AnySession) -> Any:
500
+ for successor in self.successors:
501
+ session.get_task_status(successor).mark_as_skipped()
502
+
460
503
  async def __exec_fallbacks(self, session: AnySession) -> Any:
461
504
  fallbacks: list[AnyTask] = self.fallbacks
462
505
  fallback_coros = [
@@ -464,6 +507,10 @@ class BaseTask(AnyTask):
464
507
  ]
465
508
  await asyncio.gather(*fallback_coros)
466
509
 
510
+ def __skip_fallbacks(self, session: AnySession) -> Any:
511
+ for fallback in self.fallbacks:
512
+ session.get_task_status(fallback).mark_as_skipped()
513
+
467
514
  async def _exec_action(self, ctx: AnyContext) -> Any:
468
515
  """Execute the main action of the task.
469
516
  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
zrb/task/llm_task.py CHANGED
@@ -67,6 +67,7 @@ class LLMTask(BaseTask):
67
67
  monitor_readiness: bool = False,
68
68
  upstream: list[AnyTask] | AnyTask | None = None,
69
69
  fallback: list[AnyTask] | AnyTask | None = None,
70
+ successor: list[AnyTask] | AnyTask | None = None,
70
71
  ):
71
72
  super().__init__(
72
73
  name=name,
@@ -87,6 +88,7 @@ class LLMTask(BaseTask):
87
88
  monitor_readiness=monitor_readiness,
88
89
  upstream=upstream,
89
90
  fallback=fallback,
91
+ successor=successor,
90
92
  )
91
93
  self._model = model
92
94
  self._render_model = render_model
zrb/task/make_task.py CHANGED
@@ -29,6 +29,7 @@ def make_task(
29
29
  monitor_readiness: bool = False,
30
30
  upstream: list[AnyTask] | AnyTask | None = None,
31
31
  fallback: list[AnyTask] | AnyTask | None = None,
32
+ successor: list[AnyTask] | AnyTask | None = None,
32
33
  group: AnyGroup | None = None,
33
34
  alias: str | None = None,
34
35
  ) -> Callable[[Callable[[AnyContext], Any]], AnyTask]:
@@ -53,6 +54,7 @@ def make_task(
53
54
  monitor_readiness=monitor_readiness,
54
55
  upstream=upstream,
55
56
  fallback=fallback,
57
+ successor=successor,
56
58
  )
57
59
  if group is not None:
58
60
  return group.add_task(task, alias=alias)
zrb/task/rsync_task.py CHANGED
@@ -49,6 +49,7 @@ class RsyncTask(CmdTask):
49
49
  readiness_check: list[AnyTask] | AnyTask | None = None,
50
50
  upstream: list[AnyTask] | AnyTask | None = None,
51
51
  fallback: list[AnyTask] | AnyTask | None = None,
52
+ successor: list[AnyTask] | AnyTask | None = None,
52
53
  ):
53
54
  super().__init__(
54
55
  name=name,
@@ -80,6 +81,7 @@ class RsyncTask(CmdTask):
80
81
  readiness_check=readiness_check,
81
82
  upstream=upstream,
82
83
  fallback=fallback,
84
+ successor=successor,
83
85
  )
84
86
  self._remote_source_path = remote_source_path
85
87
  self._render_remote_source_path = render_remote_source_path
zrb/task/scaffolder.py CHANGED
@@ -11,6 +11,7 @@ from zrb.input.any_input import AnyInput
11
11
  from zrb.task.any_task import AnyTask
12
12
  from zrb.task.base_task import BaseTask
13
13
  from zrb.util.attr import get_str_attr
14
+ from zrb.util.cli.style import stylize_faint
14
15
 
15
16
  TransformConfig = dict[str, str] | Callable[[AnyContext, str], str]
16
17
 
@@ -46,6 +47,7 @@ class Scaffolder(BaseTask):
46
47
  monitor_readiness: bool = False,
47
48
  upstream: list[AnyTask] | AnyTask | None = None,
48
49
  fallback: list[AnyTask] | AnyTask | None = None,
50
+ successor: list[AnyTask] | AnyTask | None = None,
49
51
  ):
50
52
  super().__init__(
51
53
  name=name,
@@ -66,6 +68,7 @@ class Scaffolder(BaseTask):
66
68
  monitor_readiness=monitor_readiness,
67
69
  upstream=upstream,
68
70
  fallback=fallback,
71
+ successor=successor,
69
72
  )
70
73
  self._source_path = source_path
71
74
  self._render_source_path = render_source_path
@@ -83,13 +86,12 @@ class Scaffolder(BaseTask):
83
86
  return get_str_attr(ctx, self._destination_path, "", auto_render=True)
84
87
 
85
88
  def _get_content_transformers(self) -> list[AnyContentTransformer]:
86
- if callable(self._content_transformers):
87
- return [
88
- ContentTransformer(match=".*", transform=self._content_transformers)
89
- ]
90
- if isinstance(self._content_transformers, dict):
89
+ if callable(self._content_transformers) or isinstance(
90
+ self._content_transformers, dict
91
+ ):
91
92
  return [
92
93
  ContentTransformer(
94
+ name="default-transform",
93
95
  match=".*",
94
96
  transform=self._content_transformers,
95
97
  auto_render=self._render_content_transformers,
@@ -109,6 +111,7 @@ class Scaffolder(BaseTask):
109
111
  for transformer in transformers:
110
112
  if transformer.match(ctx, file_path):
111
113
  try:
114
+ ctx.print(stylize_faint(f"{transformer.name}: {file_path}"))
112
115
  transformer.transform_file(ctx, file_path)
113
116
  except UnicodeDecodeError:
114
117
  pass
zrb/task/scheduler.py CHANGED
@@ -39,6 +39,7 @@ class Scheduler(BaseTrigger):
39
39
  monitor_readiness: bool = False,
40
40
  upstream: list[AnyTask] | AnyTask | None = None,
41
41
  fallback: list[AnyTask] | AnyTask | None = None,
42
+ successor: list[AnyTask] | AnyTask | None = None,
42
43
  ):
43
44
  super().__init__(
44
45
  name=name,
@@ -61,6 +62,7 @@ class Scheduler(BaseTrigger):
61
62
  monitor_readiness=monitor_readiness,
62
63
  upstream=upstream,
63
64
  fallback=fallback,
65
+ successor=successor,
64
66
  )
65
67
  self._cron_pattern = schedule
66
68
 
zrb/task/tcp_check.py CHANGED
@@ -28,6 +28,7 @@ class TcpCheck(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 TcpCheck(BaseTask):
41
42
  retries=0,
42
43
  upstream=upstream,
43
44
  fallback=fallback,
45
+ successor=successor,
44
46
  )
45
47
  self._host = host
46
48
  self._render_host = render_host
@@ -62,9 +62,10 @@ class TaskStatus:
62
62
  self._history.append((TASK_PERMANENTLY_FAILED, datetime.datetime.now()))
63
63
 
64
64
  def mark_as_terminated(self):
65
- self._is_terminated = True
66
- if not self.is_completed and not self.is_permanently_failed:
67
- self._history.append((TASK_TERMINATED, datetime.datetime.now()))
65
+ if not self._is_terminated:
66
+ self._is_terminated = True
67
+ if not (self.is_skipped or self.is_completed or self.is_permanently_failed):
68
+ self._history.append((TASK_TERMINATED, datetime.datetime.now()))
68
69
 
69
70
  @property
70
71
  def is_started(self) -> bool:
zrb/util/cmd/command.py CHANGED
@@ -62,6 +62,7 @@ async def run_command(
62
62
  log_method(line)
63
63
  return "\n".join(lines)
64
64
 
65
+ cmd_process = None
65
66
  try:
66
67
  if cwd is None:
67
68
  cwd = os.getcwd()
zrb/util/file.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import re
2
3
 
3
4
 
4
5
  def read_file(file_path: str, replace_map: dict[str, str] = {}) -> str:
@@ -14,5 +15,10 @@ def write_file(file_path: str, content: str | list[str]):
14
15
  content = "\n".join([line for line in content if line is not None])
15
16
  dir_path = os.path.dirname(file_path)
16
17
  os.makedirs(dir_path, exist_ok=True)
18
+ content = re.sub(r"\n{3,}$", "\n\n", content)
19
+ # Remove trailing newlines, but keep one if it exists
20
+ content = content.rstrip("\n")
21
+ if content.endswith("\n"):
22
+ content += "\n"
17
23
  with open(file_path, "w") as f:
18
- f.write(content.strip())
24
+ f.write(content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zrb
3
- Version: 1.0.0a21
3
+ Version: 1.0.0b2
4
4
  Summary: Your Automation Powerhouse
5
5
  Home-page: https://github.com/state-alchemists/zrb
6
6
  License: AGPL-3.0-or-later