zrb 1.0.0a12__py3-none-any.whl → 1.0.0a14__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 (65) hide show
  1. zrb/builtin/project/add/fastapp.py +32 -17
  2. zrb/builtin/project/add/fastapp_template/_zrb/column/create_column_task.py +11 -0
  3. zrb/builtin/project/add/fastapp_template/_zrb/config.py +4 -4
  4. zrb/builtin/project/add/fastapp_template/_zrb/entity/create_entity_task.py +196 -0
  5. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/my_entity_usecase.py +66 -0
  6. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/factory.py +13 -0
  7. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_db_repository.py +33 -0
  8. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_repository.py +39 -0
  9. zrb/builtin/project/add/fastapp_template/_zrb/entity/schema.template.py +29 -0
  10. zrb/builtin/project/add/fastapp_template/_zrb/group.py +9 -5
  11. zrb/builtin/project/add/fastapp_template/_zrb/helper.py +25 -11
  12. zrb/builtin/project/add/fastapp_template/_zrb/input.py +43 -0
  13. zrb/builtin/project/add/fastapp_template/_zrb/main.py +30 -21
  14. zrb/builtin/project/add/fastapp_template/_zrb/module/create_module_task.py +136 -0
  15. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/alembic.ini +117 -0
  16. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/api_client.py +6 -0
  17. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/direct_client.py +6 -0
  18. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/factory.py +9 -0
  19. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/README +1 -0
  20. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/env.py +108 -0
  21. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/script.py.mako +26 -0
  22. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/versions/3093c7336477_add_user_table.py +37 -0
  23. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration_metadata.py +3 -0
  24. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/route.py +19 -0
  25. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/service/__init__.py +0 -0
  26. zrb/builtin/project/add/fastapp_template/_zrb/module/run_module.template.py +26 -0
  27. zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +2 -5
  28. zrb/builtin/project/add/fastapp_template/common/app.py +3 -1
  29. zrb/builtin/project/add/fastapp_template/config.py +7 -7
  30. zrb/builtin/project/add/fastapp_template/module/auth/client/any_client.py +27 -0
  31. zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +2 -2
  32. zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +2 -2
  33. zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +1 -1
  34. zrb/builtin/project/add/fastapp_template/module/auth/route.py +1 -1
  35. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +2 -2
  36. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{db_repository.py → user_db_repository.py} +2 -2
  37. zrb/builtin/project/add/fastapp_template/module/auth/service/user/{usecase.py → user_usecase.py} +15 -8
  38. zrb/builtin/project/add/fastapp_template/requirements.txt +5 -6
  39. zrb/builtin/project/add/fastapp_template/schema/permission.py +31 -0
  40. zrb/builtin/project/add/fastapp_template/template.env +2 -2
  41. zrb/builtin/project/create/create.py +2 -2
  42. zrb/builtin/project/create/project-template/zrb_init.py +0 -4
  43. zrb/builtin/todo.py +19 -2
  44. zrb/config.py +1 -2
  45. zrb/content_transformer/content_transformer.py +14 -2
  46. zrb/context/context.py +13 -10
  47. zrb/input/base_input.py +2 -1
  48. zrb/runner/cli.py +16 -5
  49. zrb/runner/web_app.py +3 -3
  50. zrb/runner/web_controller/task_ui/controller.py +8 -6
  51. zrb/session/session.py +4 -1
  52. zrb/task/scaffolder.py +7 -9
  53. zrb/util/cli/style.py +7 -0
  54. zrb/util/codemod/add_code_to_module.py +12 -0
  55. zrb/util/load.py +8 -9
  56. zrb/util/string/conversion.py +52 -0
  57. zrb/util/todo.py +2 -2
  58. {zrb-1.0.0a12.dist-info → zrb-1.0.0a14.dist-info}/METADATA +1 -1
  59. {zrb-1.0.0a12.dist-info → zrb-1.0.0a14.dist-info}/RECORD +65 -41
  60. /zrb/builtin/project/add/fastapp_template/{module/auth/client/base_client.py → _zrb/module/module_template/client/any_client.py} +0 -0
  61. /zrb/builtin/project/add/fastapp_template/common/{db_repository.py → base_db_repository.py} +0 -0
  62. /zrb/builtin/project/add/fastapp_template/common/{usecase.py → base_usecase.py} +0 -0
  63. /zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{repository.py → user_repository.py} +0 -0
  64. {zrb-1.0.0a12.dist-info → zrb-1.0.0a14.dist-info}/WHEEL +0 -0
  65. {zrb-1.0.0a12.dist-info → zrb-1.0.0a14.dist-info}/entry_points.txt +0 -0
@@ -2,15 +2,15 @@ import os
2
2
 
3
3
  APP_PATH = os.path.dirname(__file__)
4
4
 
5
- APP_MODE = os.getenv("APP_NAME_MODE", "monolith")
5
+ APP_MODE = os.getenv("MY_APP_NAME_MODE", "monolith")
6
6
  APP_MODULES = [
7
7
  module.strip()
8
- for module in os.getenv("APP_NAME_MODULES", "").split(",")
8
+ for module in os.getenv("MY_APP_NAME_MODULES", "").split(",")
9
9
  if module.strip() != ""
10
10
  ]
11
- APP_PORT = int(os.getenv("APP_NAME_PORT", "3000"))
11
+ APP_PORT = int(os.getenv("MY_APP_NAME_PORT", "3000"))
12
12
  APP_COMMUNICATION = os.getenv(
13
- "APP_NAME_COMMUNICATION", "direct" if APP_MODE == "monolith" else "api"
13
+ "MY_APP_NAME_COMMUNICATION", "direct" if APP_MODE == "monolith" else "api"
14
14
  )
15
15
  APP_REPOSITORY_TYPE = os.getenv("APP_REPOSITORY_TYPE", "db")
16
16
  APP_DB_URL = os.getenv(
@@ -21,9 +21,9 @@ APP_DB_URL = os.getenv(
21
21
  else f"sqlite:///{APP_PATH}/{APP_MODULES[0]}_microservices.db"
22
22
  ),
23
23
  )
24
- APP_AUTH_SUPER_USER = os.getenv("APP_NAME_AUTH_SUPER_USER", "admin")
24
+ APP_AUTH_SUPER_USER = os.getenv("MY_APP_NAME_AUTH_SUPER_USER", "admin")
25
25
  APP_AUTH_SUPER_USER_PASSWORD = os.getenv(
26
- "APP_NAME_AUTH_SUPER_USER_PASSWORD", "secure-password"
26
+ "MY_APP_NAME_AUTH_SUPER_USER_PASSWORD", "my-secure-password"
27
27
  )
28
28
 
29
- APP_AUTH_BASE_URL = os.getenv("APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
29
+ APP_AUTH_BASE_URL = os.getenv("MY_APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from fastapp_template.schema.user import UserCreate, UserResponse, UserUpdate
4
+
5
+
6
+ class BaseClient(ABC):
7
+ @abstractmethod
8
+ async def get_user_by_id(self, user_id: str) -> UserResponse:
9
+ pass
10
+
11
+ @abstractmethod
12
+ async def get_all_users(self) -> list[UserResponse]:
13
+ pass
14
+
15
+ @abstractmethod
16
+ async def create_user(
17
+ self, data: UserCreate | list[UserCreate]
18
+ ) -> UserResponse | list[UserResponse]:
19
+ pass
20
+
21
+ @abstractmethod
22
+ async def update_user(self, user_id: str, data: UserUpdate) -> UserResponse:
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def delete_user(self, user_id: str) -> UserResponse:
27
+ pass
@@ -1,6 +1,6 @@
1
1
  from fastapp_template.config import APP_AUTH_BASE_URL
2
- from fastapp_template.module.auth.client.base_client import BaseClient
3
- from fastapp_template.module.auth.service.user.usecase import user_usecase
2
+ from fastapp_template.module.auth.client.any_client import BaseClient
3
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
4
4
 
5
5
 
6
6
  class APIClient(user_usecase.as_api_client(base_url=APP_AUTH_BASE_URL), BaseClient):
@@ -1,5 +1,5 @@
1
- from fastapp_template.module.auth.client.base_client import BaseClient
2
- from fastapp_template.module.auth.service.user.usecase import user_usecase
1
+ from fastapp_template.module.auth.client.any_client import BaseClient
2
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
3
3
 
4
4
 
5
5
  class DirectClient(user_usecase.as_direct_client(), BaseClient):
@@ -1,6 +1,6 @@
1
1
  from fastapp_template.config import APP_COMMUNICATION
2
+ from fastapp_template.module.auth.client.any_client import BaseClient
2
3
  from fastapp_template.module.auth.client.api_client import APIClient
3
- from fastapp_template.module.auth.client.base_client import BaseClient
4
4
  from fastapp_template.module.auth.client.direct_client import DirectClient
5
5
 
6
6
  if APP_COMMUNICATION == "direct":
@@ -1,7 +1,7 @@
1
1
  from fastapp_template.common.app import app
2
2
  from fastapp_template.common.schema import BasicResponse
3
3
  from fastapp_template.config import APP_MODE, APP_MODULES
4
- from fastapp_template.module.auth.service.user.usecase import user_usecase
4
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
5
5
 
6
6
  if APP_MODE == "microservices" and "auth" in APP_MODULES:
7
7
 
@@ -1,9 +1,9 @@
1
1
  from fastapp_template.common.db_engine import engine
2
2
  from fastapp_template.config import APP_REPOSITORY_TYPE
3
- from fastapp_template.module.auth.service.user.repository.db_repository import (
3
+ from fastapp_template.module.auth.service.user.repository.user_db_repository import (
4
4
  UserDBRepository,
5
5
  )
6
- from fastapp_template.module.auth.service.user.repository.repository import (
6
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
7
7
  UserRepository,
8
8
  )
9
9
 
@@ -1,6 +1,6 @@
1
- from fastapp_template.common.db_repository import BaseDBRepository
1
+ from fastapp_template.common.base_db_repository import BaseDBRepository
2
2
  from fastapp_template.common.error import NotFoundError
3
- from fastapp_template.module.auth.service.user.repository.repository import (
3
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
4
4
  UserRepository,
5
5
  )
6
6
  from fastapp_template.schema.user import User, UserCreate, UserResponse, UserUpdate
@@ -1,21 +1,28 @@
1
- from fastapp_template.common.usecase import BaseUsecase
1
+ from fastapp_template.common.base_usecase import BaseUsecase
2
2
  from fastapp_template.module.auth.service.user.repository.factory import user_repository
3
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
4
+ UserRepository,
5
+ )
3
6
  from fastapp_template.schema.user import UserCreate, UserResponse, UserUpdate
4
7
 
5
8
 
6
9
  class UserUsecase(BaseUsecase):
7
10
 
11
+ def __init__(self, user_repository: UserRepository):
12
+ super().__init__()
13
+ self.user_repository = user_repository
14
+
8
15
  @BaseUsecase.route(
9
16
  "/api/v1/users/{user_id}", methods=["get"], response_model=UserResponse
10
17
  )
11
18
  async def get_user_by_id(self, user_id: str) -> UserResponse:
12
- return await user_repository.get_by_id(user_id)
19
+ return await self.user_repository.get_by_id(user_id)
13
20
 
14
21
  @BaseUsecase.route(
15
22
  "/api/v1/users", methods=["get"], response_model=list[UserResponse]
16
23
  )
17
24
  async def get_all_users(self) -> list[UserResponse]:
18
- return await user_repository.get_all()
25
+ return await self.user_repository.get_all()
19
26
 
20
27
  @BaseUsecase.route(
21
28
  "/api/v1/users",
@@ -26,20 +33,20 @@ class UserUsecase(BaseUsecase):
26
33
  self, data: UserCreate | list[UserCreate]
27
34
  ) -> UserResponse | list[UserResponse]:
28
35
  if isinstance(data, UserCreate):
29
- return await user_repository.create(data)
30
- return await user_repository.create_bulk(data)
36
+ return await self.user_repository.create(data)
37
+ return await self.user_repository.create_bulk(data)
31
38
 
32
39
  @BaseUsecase.route(
33
40
  "/api/v1/users/{user_id}", methods=["put"], response_model=UserResponse
34
41
  )
35
42
  async def update_user(self, user_id: str, data: UserUpdate) -> UserResponse:
36
- return await user_repository.update(user_id, data)
43
+ return await self.user_repository.update(user_id, data)
37
44
 
38
45
  @BaseUsecase.route(
39
46
  "/api/v1/users/{user_id}", methods=["delete"], response_model=UserResponse
40
47
  )
41
48
  async def delete_user(self, user_id: str) -> UserResponse:
42
- return await user_repository.delete(user_id)
49
+ return await self.user_repository.delete(user_id)
43
50
 
44
51
 
45
- user_usecase = UserUsecase()
52
+ user_usecase = UserUsecase(user_repository=user_repository)
@@ -1,6 +1,5 @@
1
- fastapi[standard]==0.115.4
2
- aiosqlite==0.20.0
3
- alembic==1.14.0
4
- sqlmodel==0.0.22
5
- ulid-py==1.1.0
6
- passlib==1.7.4
1
+ fastapi[standard]~=0.115.5
2
+ alembic~=1.14.0
3
+ sqlmodel~=0.0.22
4
+ ulid-py~=1.1.0
5
+ passlib~=1.7.4
@@ -0,0 +1,31 @@
1
+ import datetime
2
+
3
+ import ulid
4
+ from sqlmodel import Field, SQLModel
5
+
6
+
7
+ class PermissionBase(SQLModel):
8
+ name: str
9
+
10
+
11
+ class PermissionCreate(PermissionBase):
12
+ description: str
13
+
14
+
15
+ class PermissionUpdate(SQLModel):
16
+ name: str | None = None
17
+ description: str | None = None
18
+
19
+
20
+ class PermissionResponse(PermissionBase):
21
+ id: str
22
+
23
+
24
+ class Permission(SQLModel, table=True):
25
+ id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
26
+ created_at: datetime.datetime | None
27
+ created_by: str | None
28
+ updated_at: datetime.datetime | None
29
+ updated_by: str | None
30
+ name: str
31
+ description: str
@@ -1,2 +1,2 @@
1
- export APP_NAME_MODE=monolith
2
- export APP_NAME_PORT=3000
1
+ export MY_APP_NAME_MODE=monolith
2
+ export MY_APP_NAME_PORT=3000
@@ -18,7 +18,7 @@ scaffold_project = Scaffolder(
18
18
  default_str=lambda _: os.getcwd(),
19
19
  ),
20
20
  StrInput(
21
- name="project-name",
21
+ name="project",
22
22
  description="Project name",
23
23
  prompt="Project name",
24
24
  default_str=lambda ctx: os.path.basename(ctx.input["project-dir"]),
@@ -27,7 +27,7 @@ scaffold_project = Scaffolder(
27
27
  source_path=os.path.join(_DIR, "project-template"),
28
28
  render_source_path=False,
29
29
  destination_path="{ctx.input['project-dir']}",
30
- transform_content={"Project Name": "{ctx.input['project-name'].title()}"},
30
+ transform_content={"Project Name": "{ctx.input.project.title()}"},
31
31
  retries=0,
32
32
  )
33
33
 
@@ -1,7 +1,3 @@
1
1
  import os
2
2
 
3
- from zrb import load_file
4
-
5
3
  _DIR = os.path.dirname(__file__)
6
-
7
- assert load_file
zrb/builtin/todo.py CHANGED
@@ -41,11 +41,13 @@ from zrb.util.todo import (
41
41
  name="project",
42
42
  description="Task project",
43
43
  prompt="Task project (space separated)",
44
+ allow_empty=True,
44
45
  ),
45
46
  StrInput(
46
47
  name="context",
47
48
  description="Task context",
48
49
  prompt="Task context (space separated)",
50
+ allow_empty=True,
49
51
  ),
50
52
  ],
51
53
  description="➕ Add todo",
@@ -175,10 +177,12 @@ def archive_todo(ctx: AnyContext):
175
177
  archive_file_path = os.path.join(TODO_DIR, "archive.txt")
176
178
  if not os.path.isdir(TODO_DIR):
177
179
  os.make_dirs(TODO_DIR, exist_ok=True)
180
+ # Get archived todo list
178
181
  archived_todo_list = []
179
182
  if os.path.isfile(archive_file_path):
180
183
  archived_todo_list = load_todo_list(archive_file_path)
181
184
  archived_todo_list += new_archived_todo_list
185
+ # Save the new todo list and add the archived ones
182
186
  save_todo_list(archive_file_path, archived_todo_list)
183
187
  save_todo_list(todo_file_path, working_todo_list)
184
188
  return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
@@ -241,9 +245,23 @@ def log_todo(ctx: AnyContext):
241
245
  log_work.append(
242
246
  {"log": ctx.input.log, "duration": ctx.input.duration, "start": ctx.input.start}
243
247
  )
248
+ # save todo with log work
244
249
  with open(log_work_file_path, "w") as f:
245
250
  f.write(json.dumps(log_work, indent=2))
246
- return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
251
+ # get log work list
252
+ task_id = todo_task.keyval.get("id", "")
253
+ log_work_path = os.path.join(TODO_DIR, "log-work", f"{task_id}.json")
254
+ log_work_list = []
255
+ if os.path.isfile(log_work_path):
256
+ with open(log_work_path, "r") as f:
257
+ log_work_list = json.loads(f.read())
258
+ return "\n".join(
259
+ [
260
+ get_visual_todo_list(todo_list, TODO_VISUAL_FILTER),
261
+ "",
262
+ get_visual_todo_card(todo_task, log_work_list),
263
+ ]
264
+ )
247
265
 
248
266
 
249
267
  def _get_default_start() -> str:
@@ -284,4 +302,3 @@ def _get_todo_txt_content() -> str:
284
302
  return ""
285
303
  with open(todo_file_path, "r") as f:
286
304
  return f.read()
287
- return f.read()
zrb/config.py CHANGED
@@ -47,8 +47,7 @@ INIT_SCRIPTS = (
47
47
  )
48
48
  LOGGING_LEVEL = _get_log_level(os.getenv("ZRB_LOGGING_LEVEL", "WARNING"))
49
49
  LOAD_BUILTIN = to_boolean(os.getenv("ZRB_LOAD_BUILTIN", "1"))
50
- ENV_PREFIX = os.getenv("ZRB_ENV", "")
51
- SHOW_PROMPT = to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1"))
50
+ SHOW_TIME = to_boolean(os.getenv("ZRB_SHOW_TIME", "1"))
52
51
  WARN_UNRECOMMENDED_COMMAND = to_boolean(
53
52
  os.getenv("ZRB_WARN_UNRECOMMENDED_COMMAND", "1")
54
53
  )
@@ -11,7 +11,10 @@ class ContentTransformer(AnyContentTransformer):
11
11
  def __init__(
12
12
  self,
13
13
  match: list[str] | str | Callable[[AnyContext, str], bool],
14
- transform: dict[str, str] | Callable[[AnyContext, str], str],
14
+ transform: (
15
+ dict[str, str | Callable[[AnyContext], str]]
16
+ | Callable[[AnyContext, str], str]
17
+ ),
15
18
  auto_render: bool = True,
16
19
  ):
17
20
  self._match = match
@@ -34,7 +37,7 @@ class ContentTransformer(AnyContentTransformer):
34
37
  if callable(self._transform_file):
35
38
  return self._transform_file(ctx, file_path)
36
39
  transform_map = {
37
- keyword: ctx.render(replacement) if self._auto_render else replacement
40
+ keyword: self._get_str_replacement(ctx, replacement)
38
41
  for keyword, replacement in self._transform_file.items()
39
42
  }
40
43
  with open(file_path, "r") as f:
@@ -43,3 +46,12 @@ class ContentTransformer(AnyContentTransformer):
43
46
  content = content.replace(keyword, replacement)
44
47
  with open(file_path, "w") as f:
45
48
  f.write(content)
49
+
50
+ def _get_str_replacement(
51
+ self, ctx: AnyContext, replacement: str | Callable[[AnyContext], str]
52
+ ) -> str:
53
+ if callable(replacement):
54
+ return replacement(ctx)
55
+ if self._auto_render:
56
+ return ctx.render(replacement)
57
+ return replacement
zrb/context/context.py CHANGED
@@ -1,22 +1,23 @@
1
1
  import datetime
2
2
  import logging
3
- import re
4
3
  import sys
5
4
  from typing import Any, TextIO
6
5
 
6
+ from zrb.config import SHOW_TIME
7
7
  from zrb.context.any_context import AnyContext
8
8
  from zrb.context.any_shared_context import AnySharedContext
9
9
  from zrb.dot_dict.dot_dict import DotDict
10
10
  from zrb.session.any_session import AnySession
11
- from zrb.util.cli.style import stylize, stylize_error, stylize_log, stylize_warning
11
+ from zrb.util.cli.style import (
12
+ remove_style,
13
+ stylize,
14
+ stylize_error,
15
+ stylize_log,
16
+ stylize_warning,
17
+ )
12
18
  from zrb.util.string.conversion import to_boolean
13
19
 
14
20
 
15
- def _remove_ansi_escape_sequences(text):
16
- ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
17
- return ansi_escape.sub("", text)
18
-
19
-
20
21
  class Context(AnyContext):
21
22
  def __init__(
22
23
  self, shared_ctx: AnySharedContext, task_name: str, color: int, icon: str
@@ -111,10 +112,12 @@ class Context(AnyContext):
111
112
  else:
112
113
  attempt_status = f"{self._attempt}/{self._max_attempt}".ljust(5)
113
114
  now = datetime.datetime.now()
114
- formatted_time = now.strftime("%y%m%d %H:%M:%S.%f")[:19]
115
- prefix = f"{formatted_time} {attempt_status} {padded_styled_task_name} "
115
+ formatted_time = (
116
+ now.strftime("%y%m%d %H:%M:%S.%f")[:19] + " " if SHOW_TIME else ""
117
+ )
118
+ prefix = f"{formatted_time}{attempt_status} {padded_styled_task_name} ⬤ "
116
119
  message = sep.join([f"{value}" for value in values])
117
- self.append_to_shared_log(_remove_ansi_escape_sequences(f"{prefix} {message}"))
120
+ self.append_to_shared_log(remove_style(f"{prefix} {message}"))
118
121
  stylized_prefix = stylize(prefix, color=color)
119
122
  print(f"{stylized_prefix} {message}", sep=sep, end=end, file=file, flush=flush)
120
123
 
zrb/input/base_input.py CHANGED
@@ -66,7 +66,8 @@ class BaseInput(AnyInput):
66
66
  default_value = self._get_default_str(shared_ctx)
67
67
  if default_value != "":
68
68
  prompt_message = f"{prompt_message} [{default_value}]"
69
- value = input(f"{prompt_message}: ")
69
+ print(f"{prompt_message}: ", end="")
70
+ value = input()
70
71
  if value.strip() == "":
71
72
  value = default_value
72
73
  return value
zrb/runner/cli.py CHANGED
@@ -82,13 +82,24 @@ class Cli(Group):
82
82
  shared_ctx = SharedContext(args=args)
83
83
  for task_input in task.inputs:
84
84
  if task_input.name in str_kwargs:
85
- continue
86
- if arg_index < len(args):
85
+ # Update shared context for next input default value
86
+ task_input.update_shared_context(
87
+ shared_ctx, str_kwargs[task_input.name]
88
+ )
89
+ elif arg_index < len(args):
87
90
  run_kwargs[task_input.name] = args[arg_index]
91
+ # Update shared context for next input default value
92
+ task_input.update_shared_context(
93
+ shared_ctx, run_kwargs[task_input.name]
94
+ )
88
95
  arg_index += 1
89
- continue
90
- str_value = task_input.prompt_cli_str(shared_ctx)
91
- run_kwargs[task_input.name] = str_value
96
+ else:
97
+ str_value = task_input.prompt_cli_str(shared_ctx)
98
+ run_kwargs[task_input.name] = str_value
99
+ # Update shared context for next input default value
100
+ task_input.update_shared_context(
101
+ shared_ctx, run_kwargs[task_input.name]
102
+ )
92
103
  return run_kwargs
93
104
 
94
105
  def _show_task_info(self, task: AnyTask):
zrb/runner/web_app.py CHANGED
@@ -64,7 +64,7 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
64
64
  # Avoid capturing '/ui' itself
65
65
  if not path:
66
66
  raise HTTPException(status_code=404, detail="Not Found")
67
- args = path.split("/")
67
+ args = path.strip("/").split("/")
68
68
  node, node_path, residual_args = extract_node_from_args(root_group, args)
69
69
  url = f"/ui/{'/'.join(node_path)}/"
70
70
  if isinstance(node, AnyTask):
@@ -82,7 +82,7 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
82
82
  """
83
83
  Creating new session
84
84
  """
85
- args = path.split("/")
85
+ args = path.strip("/").split("/")
86
86
  node, _, residual_args = extract_node_from_args(root_group, args)
87
87
  if isinstance(node, AnyTask):
88
88
  session_name = residual_args[0] if residual_args else None
@@ -107,7 +107,7 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
107
107
  """
108
108
  Getting existing session or sessions
109
109
  """
110
- args = path.split("/")
110
+ args = path.strip("/").split("/")
111
111
  node, _, residual_args = extract_node_from_args(root_group, args)
112
112
  if isinstance(node, AnyTask) and residual_args:
113
113
  if residual_args[0] == "list":
@@ -1,5 +1,6 @@
1
1
  import os
2
2
 
3
+ from zrb.context.shared_context import SharedContext
3
4
  from zrb.group.any_group import AnyGroup
4
5
  from zrb.session.any_session import AnySession
5
6
  from zrb.task.any_task import AnyTask
@@ -45,12 +46,13 @@ def handle_task_ui(
45
46
  ui_url_parts = list(api_url_parts)
46
47
  ui_url_parts[1] = "ui"
47
48
  ui_url = "/".join(ui_url_parts)
48
- task_inputs = "\n".join(
49
- [
49
+ # Assemble task inputs
50
+ input_html_list = []
51
+ for task_input in task.inputs:
52
+ task_input.update_shared_context(ctx)
53
+ input_html_list.append(
50
54
  fstring_format(_TASK_INPUT_TEMPLATE, {"task_input": task_input, "ctx": ctx})
51
- for task_input in task.inputs
52
- ]
53
- )
55
+ )
54
56
  session_name = args[0] if len(args) > 0 else ""
55
57
  return HTMLResponse(
56
58
  fstring_format(
@@ -62,7 +64,7 @@ def handle_task_ui(
62
64
  "root_description": root_group.description,
63
65
  "url": url,
64
66
  "parent_url": parent_url,
65
- "task_inputs": task_inputs,
67
+ "task_inputs": "\n".join(input_html_list),
66
68
  "api_url": api_url,
67
69
  "ui_url": ui_url,
68
70
  "main_script": _MAIN_SCRIPT,
zrb/session/session.py CHANGED
@@ -28,6 +28,7 @@ from zrb.util.cli.style import (
28
28
  ICONS,
29
29
  MAGENTA,
30
30
  YELLOW,
31
+ remove_style,
31
32
  )
32
33
  from zrb.util.group import get_node_path
33
34
  from zrb.util.string.name import get_random_name
@@ -165,7 +166,9 @@ class Session(AnySession):
165
166
  main_task_name=self._main_task.name,
166
167
  path=self.task_path,
167
168
  final_result=(
168
- f"{self.final_result}" if self.final_result is not None else ""
169
+ remove_style(f"{self.final_result}")
170
+ if self.final_result is not None
171
+ else ""
169
172
  ),
170
173
  finished=self.is_terminated,
171
174
  log=self.shared_ctx.shared_log,
zrb/task/scaffolder.py CHANGED
@@ -92,8 +92,8 @@ class Scaffolder(BaseTask):
92
92
  destination_path = self._get_destination_path(ctx)
93
93
  self._copy_path(ctx, source_path, destination_path)
94
94
  transformers = self._get_content_transformers()
95
- file_paths = self._get_all_file_paths(destination_path)
96
- for file_path in file_paths:
95
+ file_path_list = self._get_all_file_paths(destination_path)
96
+ for file_path in file_path_list:
97
97
  for transformer in transformers:
98
98
  if transformer.match(ctx, file_path):
99
99
  try:
@@ -118,14 +118,10 @@ class Scaffolder(BaseTask):
118
118
  dest_dir, self._transform_path(ctx, file_name)
119
119
  )
120
120
  shutil.copy2(src_file, dest_file)
121
- ctx.log_info(f"Copied and renamed {src_file} to {dest_file}")
121
+ ctx.log_info(f"Copied {src_file} to {dest_file}")
122
122
  else:
123
- dest_file = os.path.join(
124
- destination_path,
125
- self._transform_path(ctx, os.path.basename(source_path)),
126
- )
127
- shutil.copy2(source_path, dest_file)
128
- ctx.log_info(f"Copied and renamed {source_path} to {dest_file}")
123
+ shutil.copy2(source_path, destination_path)
124
+ ctx.log_info(f"Copied {source_path} to {destination_path}")
129
125
 
130
126
  def _transform_path(self, ctx: AnyContext, file_path: str):
131
127
  if callable(self._path_transformer):
@@ -139,6 +135,8 @@ class Scaffolder(BaseTask):
139
135
  """
140
136
  Returns a list of absolute file paths for all files in the given path, recursively.
141
137
  """
138
+ if os.path.isfile(path):
139
+ return [os.path.abspath(path)]
142
140
  file_paths = []
143
141
  for root, _, files in os.walk(path):
144
142
  for file in files:
zrb/util/cli/style.py CHANGED
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  BLACK = 30
2
4
  RED = 31
3
5
  GREEN = 32
@@ -118,6 +120,11 @@ ICONS = [
118
120
  ]
119
121
 
120
122
 
123
+ def remove_style(text):
124
+ ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
125
+ return ansi_escape.sub("", text)
126
+
127
+
121
128
  def stylize(
122
129
  text: str,
123
130
  color: int | None = None,
@@ -0,0 +1,12 @@
1
+ def add_code_to_module(source_code: str, new_code: str) -> str:
2
+ lines = source_code.splitlines()
3
+ last_import_index = -1
4
+ for i, line in enumerate(lines):
5
+ stripped_line = line.strip()
6
+ if stripped_line.startswith("import") or stripped_line.startswith("from"):
7
+ last_import_index = i
8
+ elif stripped_line and not stripped_line.startswith("#"):
9
+ break
10
+ if last_import_index != -1:
11
+ lines.insert(last_import_index + 1, new_code)
12
+ return "\n".join(lines)
zrb/util/load.py CHANGED
@@ -9,33 +9,33 @@ from typing import Any
9
9
  pattern = re.compile("[^a-zA-Z0-9]")
10
10
 
11
11
 
12
- def load_zrb_init(dir_path: str | None = None):
12
+ def load_zrb_init(dir_path: str | None = None) -> Any | None:
13
13
  if dir_path is None:
14
14
  dir_path = os.getcwd()
15
15
  script_path = os.path.join(dir_path, "zrb_init.py")
16
16
  if os.path.isfile(script_path):
17
- load_file(script_path, -1)
18
- return
17
+ return load_file(script_path, -1)
19
18
  new_dir_path = os.path.dirname(dir_path)
20
19
  if new_dir_path == dir_path:
21
20
  return
22
- load_zrb_init(new_dir_path)
21
+ return load_zrb_init(new_dir_path)
23
22
 
24
23
 
25
- @lru_cache()
26
- def load_file(script_path: str, sys_path_index: int = 0):
24
+ @lru_cache
25
+ def load_file(script_path: str, sys_path_index: int = 0) -> Any | None:
27
26
  if not os.path.isfile(script_path):
28
- return
27
+ return None
28
+ module_name = pattern.sub("", script_path)
29
29
  # Append script dir path
30
30
  script_dir_path = os.path.dirname(script_path)
31
31
  if script_dir_path not in sys.path:
32
32
  sys.path.insert(sys_path_index, script_dir_path)
33
33
  # Add script dir path to Python path
34
34
  os.environ["PYTHONPATH"] = _get_new_python_path(script_dir_path)
35
- module_name = pattern.sub("", script_path)
36
35
  spec = importlib.util.spec_from_file_location(module_name, script_path)
37
36
  module = importlib.util.module_from_spec(spec)
38
37
  spec.loader.exec_module(module)
38
+ return module
39
39
 
40
40
 
41
41
  def _get_new_python_path(dir_path: str) -> str:
@@ -47,7 +47,6 @@ def _get_new_python_path(dir_path: str) -> str:
47
47
  return ":".join([current_python_path, dir_path])
48
48
 
49
49
 
50
- @lru_cache()
51
50
  def load_module(module_name: str) -> Any:
52
51
  module = importlib.import_module(module_name)
53
52
  return module