zrb 1.0.0a12__py3-none-any.whl → 1.0.0a15__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 (71) hide show
  1. zrb/builtin/git.py +22 -20
  2. zrb/builtin/git_subtree.py +12 -9
  3. zrb/builtin/project/add/fastapp.py +32 -17
  4. zrb/builtin/project/add/fastapp_template/_zrb/column/create_column_task.py +11 -0
  5. zrb/builtin/project/add/fastapp_template/_zrb/config.py +4 -4
  6. zrb/builtin/project/add/fastapp_template/_zrb/entity/create_entity_task.py +196 -0
  7. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/my_entity_usecase.py +66 -0
  8. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/factory.py +13 -0
  9. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_db_repository.py +33 -0
  10. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_repository.py +39 -0
  11. zrb/builtin/project/add/fastapp_template/_zrb/entity/schema.template.py +29 -0
  12. zrb/builtin/project/add/fastapp_template/_zrb/group.py +9 -5
  13. zrb/builtin/project/add/fastapp_template/_zrb/helper.py +25 -11
  14. zrb/builtin/project/add/fastapp_template/_zrb/input.py +43 -0
  15. zrb/builtin/project/add/fastapp_template/_zrb/main.py +30 -21
  16. zrb/builtin/project/add/fastapp_template/_zrb/module/create_module_task.py +136 -0
  17. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/alembic.ini +117 -0
  18. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/api_client.py +6 -0
  19. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/direct_client.py +6 -0
  20. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/factory.py +9 -0
  21. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/README +1 -0
  22. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/env.py +108 -0
  23. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/script.py.mako +26 -0
  24. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/versions/3093c7336477_add_user_table.py +37 -0
  25. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration_metadata.py +3 -0
  26. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/route.py +19 -0
  27. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/service/__init__.py +0 -0
  28. zrb/builtin/project/add/fastapp_template/_zrb/module/run_module.template.py +26 -0
  29. zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +2 -5
  30. zrb/builtin/project/add/fastapp_template/common/app.py +3 -1
  31. zrb/builtin/project/add/fastapp_template/config.py +7 -7
  32. zrb/builtin/project/add/fastapp_template/module/auth/client/any_client.py +27 -0
  33. zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +2 -2
  34. zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +2 -2
  35. zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +1 -1
  36. zrb/builtin/project/add/fastapp_template/module/auth/route.py +1 -1
  37. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +2 -2
  38. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{db_repository.py → user_db_repository.py} +2 -2
  39. zrb/builtin/project/add/fastapp_template/module/auth/service/user/{usecase.py → user_usecase.py} +15 -8
  40. zrb/builtin/project/add/fastapp_template/requirements.txt +5 -6
  41. zrb/builtin/project/add/fastapp_template/schema/permission.py +31 -0
  42. zrb/builtin/project/add/fastapp_template/template.env +2 -2
  43. zrb/builtin/project/create/create.py +2 -2
  44. zrb/builtin/project/create/project-template/zrb_init.py +0 -4
  45. zrb/builtin/todo.py +19 -2
  46. zrb/config.py +1 -2
  47. zrb/content_transformer/content_transformer.py +14 -2
  48. zrb/context/context.py +13 -10
  49. zrb/input/base_input.py +2 -1
  50. zrb/runner/cli.py +16 -5
  51. zrb/runner/web_app.py +3 -3
  52. zrb/runner/web_controller/task_ui/controller.py +8 -6
  53. zrb/session/session.py +4 -1
  54. zrb/task/cmd_task.py +17 -50
  55. zrb/task/scaffolder.py +7 -9
  56. zrb/util/cli/style.py +7 -0
  57. zrb/util/cmd/command.py +60 -0
  58. zrb/util/codemod/add_code_to_module.py +12 -0
  59. zrb/util/git.py +115 -133
  60. zrb/util/git_subtree.py +73 -55
  61. zrb/util/load.py +8 -9
  62. zrb/util/string/conversion.py +52 -0
  63. zrb/util/todo.py +2 -2
  64. {zrb-1.0.0a12.dist-info → zrb-1.0.0a15.dist-info}/METADATA +1 -1
  65. {zrb-1.0.0a12.dist-info → zrb-1.0.0a15.dist-info}/RECORD +71 -47
  66. /zrb/builtin/project/add/fastapp_template/{module/auth/client/base_client.py → _zrb/module/module_template/client/any_client.py} +0 -0
  67. /zrb/builtin/project/add/fastapp_template/common/{db_repository.py → base_db_repository.py} +0 -0
  68. /zrb/builtin/project/add/fastapp_template/common/{usecase.py → base_usecase.py} +0 -0
  69. /zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{repository.py → user_repository.py} +0 -0
  70. {zrb-1.0.0a12.dist-info → zrb-1.0.0a15.dist-info}/WHEEL +0 -0
  71. {zrb-1.0.0a12.dist-info → zrb-1.0.0a15.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,37 @@
1
+ """Add user table
2
+
3
+ Revision ID: 3093c7336477
4
+ Revises:
5
+ Create Date: 2024-11-20 05:57:01.684118
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ import sqlmodel
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "3093c7336477"
17
+ down_revision: Union[str, None] = None
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "user",
26
+ sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
27
+ sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
28
+ sa.Column("password", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
29
+ sa.PrimaryKeyConstraint("id"),
30
+ )
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade() -> None:
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ op.drop_table("user")
37
+ # ### end Alembic commands ###
@@ -0,0 +1,3 @@
1
+ from sqlalchemy import MetaData
2
+
3
+ metadata = MetaData()
@@ -0,0 +1,19 @@
1
+ from fastapp_template.common.app import app
2
+ from fastapp_template.common.schema import BasicResponse
3
+ from fastapp_template.config import APP_MODE, APP_MODULES
4
+
5
+ if APP_MODE == "microservices" and "auth" in APP_MODULES:
6
+
7
+ if APP_MODE == "microservices" and (
8
+ len(APP_MODULES) > 0 and APP_MODULES[0] == "auth"
9
+ ):
10
+
11
+ @app.api_route("/health", methods=["GET", "HEAD"], response_model=BasicResponse)
12
+ async def health():
13
+ return BasicResponse(message="ok")
14
+
15
+ @app.api_route(
16
+ "/readiness", methods=["GET", "HEAD"], response_model=BasicResponse
17
+ )
18
+ async def readiness():
19
+ return BasicResponse(message="ok")
@@ -0,0 +1,26 @@
1
+ # 🔐 Run/Migrate My Module ==========================================================
2
+
3
+ run_my_module = app_run_group.add_task(
4
+ run_microservice("my-module", 3000, "my_module"), alias="microservices-my_module"
5
+ )
6
+ prepare_venv >> run_my_module >> run_microservices
7
+
8
+ create_my_module_migration = app_create_migration_group.add_task(
9
+ create_migration("my-module", "my_module"), alias="my_module"
10
+ )
11
+ prepare_venv >> create_my_module_migration >> create_all_migration
12
+
13
+ migrate_monolith_my_module = migrate_module(
14
+ "my_module", "my_module", as_microservices=False
15
+ )
16
+ prepare_venv >> migrate_monolith_my_module >> [migrate_monolith, run_monolith]
17
+
18
+ migrate_microservices_my_module = app_migrate_group.add_task(
19
+ migrate_module("my-module", "my_module", as_microservices=True),
20
+ alias="microservices-my-module",
21
+ )
22
+ (
23
+ prepare_venv
24
+ >> migrate_microservices_my_module
25
+ >> [migrate_microservices, run_my_module]
26
+ )
@@ -1,22 +1,19 @@
1
1
  import os
2
2
 
3
3
  from fastapp_template._zrb.config import ACTIVATE_VENV_SCRIPT, APP_DIR
4
- from fastapp_template._zrb.group import app_group
5
4
 
6
5
  from zrb import CmdTask
7
6
 
8
7
  create_venv = CmdTask(
9
- name="create-app-name-venv",
8
+ name="create-my-app-name-venv",
10
9
  cwd=APP_DIR,
11
10
  cmd="python -m venv .venv",
12
11
  execute_condition=lambda _: not os.path.isdir(os.path.join(APP_DIR, ".venv")),
13
12
  )
14
13
 
15
14
  prepare_venv = CmdTask(
16
- name="prepare-app-name-venv",
15
+ name="prepare-my-app-name-venv",
17
16
  cmd=[ACTIVATE_VENV_SCRIPT, "pip install -r requirements.txt"],
18
17
  cwd=APP_DIR,
19
18
  )
20
19
  create_venv >> prepare_venv
21
-
22
- app_group.add_task(create_venv, alias="prepare")
@@ -13,6 +13,8 @@ async def lifespan(app: FastAPI):
13
13
 
14
14
 
15
15
  app_title = (
16
- "App Name" if APP_MODE == "monolith" else f"App Name - {', '.join(APP_MODULES)}"
16
+ "My App Name"
17
+ if APP_MODE == "monolith"
18
+ else f"My App Name - {', '.join(APP_MODULES)}"
17
19
  )
18
20
  app = FastAPI(title=app_title, lifespan=lifespan)
@@ -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,