zrb 1.0.0b8__py3-none-any.whl → 1.0.0b10__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 (81) hide show
  1. zrb/__main__.py +3 -0
  2. zrb/builtin/project/add/fastapp/fastapp_task.py +1 -0
  3. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
  4. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
  5. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +4 -4
  6. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +108 -1
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +67 -4
  9. 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 +5 -5
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +1 -0
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_create_my_entity.py +53 -0
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_delete_my_entity.py +62 -0
  13. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_read_my_entity.py +65 -0
  14. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_update_my_entity.py +61 -0
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +57 -13
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +8 -0
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +2 -2
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +6 -1
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +65 -14
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +106 -0
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +6 -86
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +27 -11
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +140 -51
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +15 -0
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +1 -1
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +22 -4
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +21 -0
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +106 -61
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +3 -4
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +15 -14
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +4 -4
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +24 -5
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +14 -12
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +134 -97
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +28 -11
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +215 -13
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +30 -2
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +216 -41
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +57 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +7 -1
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +2 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +13 -12
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +64 -12
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +17 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
  56. zrb/task/base_task.py +10 -10
  57. zrb/task/cmd_task.py +2 -5
  58. zrb/util/cmd/command.py +39 -48
  59. zrb/util/codemod/modification_mode.py +3 -0
  60. zrb/util/codemod/modify_class.py +58 -0
  61. zrb/util/codemod/modify_class_parent.py +68 -0
  62. zrb/util/codemod/modify_class_property.py +128 -0
  63. zrb/util/codemod/modify_dict.py +75 -0
  64. zrb/util/codemod/modify_function.py +65 -0
  65. zrb/util/codemod/modify_function_call.py +68 -0
  66. zrb/util/codemod/modify_method.py +88 -0
  67. zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
  68. zrb/util/file.py +3 -2
  69. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/METADATA +2 -1
  70. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/RECORD +72 -55
  71. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
  72. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +0 -48
  73. zrb/util/codemod/append_code_to_class.py +0 -35
  74. zrb/util/codemod/append_code_to_function.py +0 -38
  75. zrb/util/codemod/append_code_to_method.py +0 -55
  76. zrb/util/codemod/append_key_to_dict.py +0 -51
  77. zrb/util/codemod/append_param_to_function_call.py +0 -39
  78. zrb/util/codemod/prepend_parent_to_class.py +0 -38
  79. zrb/util/codemod/prepend_property_to_class.py +0 -55
  80. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/WHEEL +0 -0
  81. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/entry_points.txt +0 -0
@@ -3,18 +3,26 @@
3
3
 
4
4
  @app.get("/api/v1/my-entities", response_model=MultipleMyEntityResponse)
5
5
  async def get_my_entities(
6
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
6
7
  page: int = 1,
7
8
  page_size: int = 10,
8
9
  sort: str | None = None,
9
10
  filter: str | None = None,
10
11
  ) -> MultipleMyEntityResponse:
12
+ if not current_user.has_permission("my-entity:read"):
13
+ raise ForbiddenError("Access denied")
11
14
  return await my_module_client.get_my_entities(
12
15
  page=page, page_size=page_size, sort=sort, filter=filter
13
16
  )
14
17
 
15
18
 
16
19
  @app.get("/api/v1/my-entities/{my_entity_id}", response_model=MyEntityResponse)
17
- async def get_my_entity_by_id(my_entity_id: str) -> MyEntityResponse:
20
+ async def get_my_entity_by_id(
21
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
22
+ my_entity_id: str,
23
+ ) -> MyEntityResponse:
24
+ if not current_user.has_permission("my-entity:read"):
25
+ raise ForbiddenError("Access denied")
18
26
  return await my_module_client.get_my_entity_by_id(my_entity_id)
19
27
 
20
28
 
@@ -22,9 +30,14 @@ async def get_my_entity_by_id(my_entity_id: str) -> MyEntityResponse:
22
30
  "/api/v1/my-entities/bulk",
23
31
  response_model=list[MyEntityResponse],
24
32
  )
25
- async def create_my_entity_bulk(data: list[MyEntityCreate]):
33
+ async def create_my_entity_bulk(
34
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
35
+ data: list[MyEntityCreate],
36
+ ) -> list[MyEntityResponse]:
37
+ if not current_user.has_permission("my-entity:create"):
38
+ raise ForbiddenError("Access denied")
26
39
  return await my_module_client.create_my_entity_bulk(
27
- [row.with_audit(created_by="system") for row in data]
40
+ [row.with_audit(created_by=current_user.id) for row in data]
28
41
  )
29
42
 
30
43
 
@@ -32,17 +45,30 @@ async def create_my_entity_bulk(data: list[MyEntityCreate]):
32
45
  "/api/v1/my-entities",
33
46
  response_model=MyEntityResponse,
34
47
  )
35
- async def create_my_entity(data: MyEntityCreate):
36
- return await my_module_client.create_my_entity(data.with_audit(created_by="system"))
48
+ async def create_my_entity(
49
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
50
+ data: MyEntityCreate,
51
+ ) -> MyEntityResponse:
52
+ if not current_user.has_permission("my-entity:create"):
53
+ raise ForbiddenError("Access denied")
54
+ return await my_module_client.create_my_entity(
55
+ data.with_audit(created_by=current_user.id)
56
+ )
37
57
 
38
58
 
39
59
  @app.put(
40
60
  "/api/v1/my-entities/bulk",
41
61
  response_model=list[MyEntityResponse],
42
62
  )
43
- async def update_my_entity_bulk(my_entity_ids: list[str], data: MyEntityUpdate):
63
+ async def update_my_entity_bulk(
64
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
65
+ my_entity_ids: list[str],
66
+ data: MyEntityUpdate,
67
+ ) -> list[MyEntityResponse]:
68
+ if not current_user.has_permission("my-entity:update"):
69
+ raise ForbiddenError("Access denied")
44
70
  return await my_module_client.update_my_entity_bulk(
45
- my_entity_ids, data.with_audit(updated_by="system")
71
+ my_entity_ids, data.with_audit(updated_by=current_user.id)
46
72
  )
47
73
 
48
74
 
@@ -50,9 +76,15 @@ async def update_my_entity_bulk(my_entity_ids: list[str], data: MyEntityUpdate):
50
76
  "/api/v1/my-entities/{my_entity_id}",
51
77
  response_model=MyEntityResponse,
52
78
  )
53
- async def update_my_entity(my_entity_id: str, data: MyEntityUpdate):
79
+ async def update_my_entity(
80
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
81
+ my_entity_id: str,
82
+ data: MyEntityUpdate,
83
+ ) -> MyEntityResponse:
84
+ if not current_user.has_permission("my-entity:update"):
85
+ raise ForbiddenError("Access denied")
54
86
  return await my_module_client.update_my_entity(
55
- my_entity_id, data.with_audit(updated_by="system")
87
+ my_entity_id, data.with_audit(updated_by=current_user.id)
56
88
  )
57
89
 
58
90
 
@@ -60,9 +92,14 @@ async def update_my_entity(my_entity_id: str, data: MyEntityUpdate):
60
92
  "/api/v1/my-entities/bulk",
61
93
  response_model=list[MyEntityResponse],
62
94
  )
63
- async def delete_my_entity_bulk(my_entity_ids: list[str]):
95
+ async def delete_my_entity_bulk(
96
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
97
+ my_entity_ids: list[str],
98
+ ) -> list[MyEntityResponse]:
99
+ if not current_user.has_permission("my-entity:delete"):
100
+ raise ForbiddenError("Access denied")
64
101
  return await my_module_client.delete_my_entity_bulk(
65
- my_entity_ids, deleted_by="system"
102
+ my_entity_ids, deleted_by=current_user.id
66
103
  )
67
104
 
68
105
 
@@ -70,5 +107,12 @@ async def delete_my_entity_bulk(my_entity_ids: list[str]):
70
107
  "/api/v1/my-entities/{my_entity_id}",
71
108
  response_model=MyEntityResponse,
72
109
  )
73
- async def delete_my_entity(my_entity_id: str):
74
- return await my_module_client.delete_my_entity(my_entity_id, deleted_by="system")
110
+ async def delete_my_entity(
111
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
112
+ my_entity_id: str,
113
+ ) -> MyEntityResponse:
114
+ if not current_user.has_permission("my-entity:delete"):
115
+ raise ForbiddenError("Access denied")
116
+ return await my_module_client.delete_my_entity(
117
+ my_entity_id, deleted_by=current_user.id
118
+ )
@@ -3,6 +3,14 @@ from my_app_name._zrb.util import get_existing_module_names, get_existing_schema
3
3
  from zrb import OptionInput, StrInput
4
4
  from zrb.util.string.conversion import pluralize
5
5
 
6
+ run_env_input = OptionInput(
7
+ name="env",
8
+ description="Running environment",
9
+ prompt="Running Environment",
10
+ options=["dev", "prod"],
11
+ default_str="prod",
12
+ )
13
+
6
14
  new_module_input = StrInput(
7
15
  name="module", description="Module name", prompt="New module name"
8
16
  )
@@ -4,8 +4,8 @@ from my_app_name._zrb.config import APP_DIR
4
4
  from my_app_name._zrb.util import get_existing_module_names
5
5
 
6
6
  from zrb.context.any_context import AnyContext
7
- from zrb.util.codemod.append_code_to_function import append_code_to_function
8
- from zrb.util.codemod.append_key_to_dict import append_key_to_dict
7
+ from zrb.util.codemod.modify_dict import append_key_to_dict
8
+ from zrb.util.codemod.modify_function import append_code_to_function
9
9
  from zrb.util.file import read_file, write_file
10
10
  from zrb.util.string.conversion import to_kebab_case, to_pascal_case, to_snake_case
11
11
 
@@ -1,4 +1,9 @@
1
- from fastapi import FastAPI
1
+ from typing import Annotated
2
+
3
+ from fastapi import Depends, FastAPI
4
+ from my_app_name.common.error import ForbiddenError
5
+ from my_app_name.module.gateway.util.auth import get_current_user
6
+ from my_app_name.schema.user import AuthUserResponse
2
7
 
3
8
 
4
9
  def serve_my_module_route(app: FastAPI):
@@ -1,26 +1,30 @@
1
1
  # 🔐 Run/Migrate My Module ==========================================================
2
2
 
3
3
  run_my_module = app_run_group.add_task(
4
- run_microservice("my-module", 3000, "my_module"), alias="svc-my_module"
4
+ run_microservice("my_module", 3000), alias="svc-my-module"
5
5
  )
6
6
  prepare_venv >> run_my_module >> run_microservices
7
7
 
8
8
  create_my_module_migration = app_create_migration_group.add_task(
9
- create_migration("my-module", "my_module"), alias="my_module"
9
+ create_migration("my_module"), alias="my_module"
10
10
  )
11
11
  prepare_venv >> create_my_module_migration >> create_all_migration
12
12
 
13
- migrate_monolith_my_module = migrate_module(
14
- "my_module", "my_module", as_microservices=False
15
- )
13
+ migrate_monolith_my_module = migrate_module("my_module", as_microservices=False)
16
14
  prepare_venv >> migrate_monolith_my_module >> [migrate_monolith, run_monolith]
17
15
 
18
16
  migrate_microservices_my_module = app_migrate_group.add_task(
19
- migrate_module("my-module", "my_module", as_microservices=True),
17
+ migrate_module("my_module", as_microservices=True),
20
18
  alias="svc-my-module",
21
19
  )
20
+
22
21
  (
23
22
  prepare_venv
24
23
  >> migrate_microservices_my_module
25
24
  >> [migrate_microservices, run_my_module]
26
25
  )
26
+
27
+ migrate_test_my_module = migrate_module(
28
+ "my_module", as_microservices=False, additional_env_vars=TEST_ENV_VARS
29
+ )
30
+ prepare_venv >> migrate_test_my_module >> migrate_test
@@ -1,25 +1,66 @@
1
1
  import os
2
2
 
3
3
  from my_app_name._zrb.column.add_column_task import add_my_app_name_column
4
- from my_app_name._zrb.config import ACTIVATE_VENV_SCRIPT, APP_DIR, MONOLITH_ENV_VARS
4
+ from my_app_name._zrb.config import (
5
+ ACTIVATE_VENV_SCRIPT,
6
+ APP_DIR,
7
+ MONOLITH_ENV_VARS,
8
+ TEST_ENV_VARS,
9
+ )
5
10
  from my_app_name._zrb.entity.add_entity_task import add_my_app_name_entity
6
11
  from my_app_name._zrb.format_task import format_my_app_name_code
7
12
  from my_app_name._zrb.group import (
8
13
  app_create_migration_group,
14
+ app_group,
9
15
  app_migrate_group,
10
16
  app_run_group,
11
17
  )
18
+ from my_app_name._zrb.input import run_env_input
12
19
  from my_app_name._zrb.module.add_module_task import add_my_app_name_module
13
- from my_app_name._zrb.util import create_migration, migrate_module, run_microservice
20
+ from my_app_name._zrb.task_util import (
21
+ create_migration,
22
+ migrate_module,
23
+ run_microservice,
24
+ run_my_app_name,
25
+ )
14
26
  from my_app_name._zrb.venv_task import prepare_venv
15
27
 
16
- from zrb import CmdTask, EnvFile, EnvMap, Task
28
+ from zrb import AnyContext, CmdPath, CmdTask, EnvFile, EnvMap, Task, make_task
17
29
 
18
30
  assert add_my_app_name_entity
19
31
  assert add_my_app_name_module
20
32
  assert add_my_app_name_column
21
33
  assert format_my_app_name_code
22
34
 
35
+ # 🧪 Test ======================================================================
36
+
37
+
38
+ @make_task(
39
+ name="prepare-my-app-name-test",
40
+ )
41
+ def prepare_test(ctx: AnyContext):
42
+ db_test_path = os.path.join(APP_DIR, "test.db")
43
+ if os.path.exists(db_test_path):
44
+ ctx.print(f"Removing test db: {db_test_path}")
45
+ os.remove(db_test_path)
46
+
47
+
48
+ migrate_test = Task(name="migrate-test")
49
+
50
+ test_app = app_group.add_task(
51
+ CmdTask(
52
+ name="test-my-app-name",
53
+ description="🧪 Test My App Name",
54
+ env=EnvMap(vars=TEST_ENV_VARS),
55
+ cwd=APP_DIR,
56
+ cmd=CmdPath(os.path.join(APP_DIR, "test.sh")),
57
+ retries=0,
58
+ ),
59
+ alias="test",
60
+ )
61
+ prepare_venv >> prepare_test >> migrate_test >> test_app
62
+
63
+
23
64
  # 🚀 Run/Migrate All ===========================================================
24
65
 
25
66
  run_all = app_run_group.add_task(
@@ -52,6 +93,7 @@ run_monolith = app_run_group.add_task(
52
93
  CmdTask(
53
94
  name="run-monolith-my-app-name",
54
95
  description="🗿 Run My App Name as a monolith",
96
+ input=run_env_input,
55
97
  env=[
56
98
  EnvFile(path=os.path.join(APP_DIR, "template.env")),
57
99
  EnvMap(vars=MONOLITH_ENV_VARS),
@@ -59,7 +101,7 @@ run_monolith = app_run_group.add_task(
59
101
  cwd=APP_DIR,
60
102
  cmd=[
61
103
  ACTIVATE_VENV_SCRIPT,
62
- 'fastapi dev main.py --port "${MY_APP_NAME_PORT}"',
104
+ run_my_app_name,
63
105
  ],
64
106
  render_cmd=False,
65
107
  retries=2,
@@ -100,40 +142,49 @@ migrate_microservices >> migrate_all
100
142
  # 📡 Run/Migrate Gateway =======================================================
101
143
 
102
144
  run_gateway = app_run_group.add_task(
103
- run_microservice("gateway", 3001, "gateway"), alias="svc-gateway"
145
+ run_microservice("gateway", 3001), alias="svc-gateway"
104
146
  )
105
147
  prepare_venv >> run_gateway >> run_microservices
106
148
 
107
149
  create_gateway_migration = app_create_migration_group.add_task(
108
- create_migration("gateway", "gateway"), alias="gateway"
150
+ create_migration("gateway"), alias="gateway"
109
151
  )
110
152
  prepare_venv >> create_gateway_migration >> create_all_migration
111
153
 
112
- migrate_monolith_gateway = migrate_module("gateway", "gateway", as_microservices=False)
154
+ migrate_monolith_gateway = migrate_module("gateway", as_microservices=False)
113
155
  prepare_venv >> migrate_monolith_gateway >> [migrate_monolith, run_monolith]
114
156
 
115
157
  migrate_microservices_gateway = app_migrate_group.add_task(
116
- migrate_module("gateway", "gateway", as_microservices=True),
158
+ migrate_module("gateway", as_microservices=True),
117
159
  alias="svc-gateway",
118
160
  )
119
161
  prepare_venv >> migrate_microservices_gateway >> [migrate_microservices, run_gateway]
120
162
 
163
+ migrate_test_gateway = migrate_module(
164
+ "gateway", as_microservices=False, additional_env_vars=TEST_ENV_VARS
165
+ )
166
+ prepare_venv >> migrate_test_gateway >> migrate_test
167
+
168
+
121
169
  # 🔐 Run/Migrate Auth ==========================================================
122
170
 
123
- run_auth = app_run_group.add_task(
124
- run_microservice("auth", 3002, "auth"), alias="svc-auth"
125
- )
171
+ run_auth = app_run_group.add_task(run_microservice("auth", 3002), alias="svc-auth")
126
172
  prepare_venv >> run_auth >> run_microservices
127
173
 
128
174
  create_auth_migration = app_create_migration_group.add_task(
129
- create_migration("auth", "auth"), alias="auth"
175
+ create_migration("auth"), alias="auth"
130
176
  )
131
177
  prepare_venv >> create_auth_migration >> create_all_migration
132
178
 
133
- migrate_monolith_auth = migrate_module("auth", "auth", as_microservices=False)
179
+ migrate_monolith_auth = migrate_module("auth", as_microservices=False)
134
180
  prepare_venv >> migrate_monolith_auth >> [migrate_monolith, run_monolith]
135
181
 
136
182
  migrate_microservices_auth = app_migrate_group.add_task(
137
- migrate_module("auth", "auth", as_microservices=True), alias="svc-auth"
183
+ migrate_module("auth", as_microservices=True), alias="svc-auth"
138
184
  )
139
185
  prepare_venv >> migrate_microservices_auth >> [migrate_microservices, run_auth]
186
+
187
+ migrate_test_auth = migrate_module(
188
+ "auth", as_microservices=False, additional_env_vars=TEST_ENV_VARS
189
+ )
190
+ prepare_venv >> migrate_test_auth >> migrate_test
@@ -0,0 +1,106 @@
1
+ import os
2
+
3
+ from my_app_name._zrb.config import (
4
+ ACTIVATE_VENV_SCRIPT,
5
+ APP_DIR,
6
+ MICROSERVICES_ENV_VARS,
7
+ MONOLITH_ENV_VARS,
8
+ )
9
+ from my_app_name._zrb.input import run_env_input
10
+ from my_app_name._zrb.util import (
11
+ cd_module_script,
12
+ run_my_app_name,
13
+ set_create_migration_db_url_env,
14
+ set_env,
15
+ set_module_env,
16
+ )
17
+
18
+ from zrb import Cmd, CmdTask, EnvFile, EnvMap, StrInput, Task
19
+ from zrb.util.string.conversion import to_kebab_case, to_snake_case
20
+
21
+
22
+ def create_migration(module: str) -> Task:
23
+ name = to_kebab_case(module)
24
+ return CmdTask(
25
+ name=f"create-my-app-name-{name}-migration",
26
+ description=f"🧩 Create My App Name {name.capitalize()} DB migration",
27
+ input=StrInput(
28
+ name="message",
29
+ description="Migration message",
30
+ prompt="Migration message",
31
+ allow_empty=False,
32
+ ),
33
+ env=EnvFile(path=os.path.join(APP_DIR, "template.env")),
34
+ cwd=APP_DIR,
35
+ cmd=[
36
+ ACTIVATE_VENV_SCRIPT,
37
+ set_create_migration_db_url_env(module),
38
+ set_module_env(module),
39
+ cd_module_script(module),
40
+ "alembic upgrade head",
41
+ Cmd(
42
+ "alembic revision --autogenerate -m {double_quote(ctx.input.message)}",
43
+ auto_render=True,
44
+ ),
45
+ ],
46
+ render_cmd=False,
47
+ retries=2,
48
+ )
49
+
50
+
51
+ def migrate_module(
52
+ module: str, as_microservices: bool, additional_env_vars: dict[str, str] = {}
53
+ ) -> Task:
54
+ name = to_kebab_case(module)
55
+ env_vars = (
56
+ dict(MICROSERVICES_ENV_VARS) if as_microservices else dict(MONOLITH_ENV_VARS)
57
+ )
58
+ env_vars.update(additional_env_vars)
59
+ if as_microservices:
60
+ env_vars["MY_APP_NAME_MODULES"] = to_snake_case(module)
61
+ return CmdTask(
62
+ name=(
63
+ f"migrate-my-app-name-{name}"
64
+ if as_microservices
65
+ else f"migrate-{name}-on-monolith"
66
+ ),
67
+ description=f"🧩 Run My App Name {name.capitalize()} DB migration",
68
+ env=[
69
+ EnvFile(path=os.path.join(APP_DIR, "template.env")),
70
+ EnvMap(vars=env_vars),
71
+ ],
72
+ cwd=APP_DIR,
73
+ cmd=[
74
+ ACTIVATE_VENV_SCRIPT,
75
+ cd_module_script(module),
76
+ "alembic upgrade head",
77
+ ],
78
+ render_cmd=False,
79
+ retries=2,
80
+ )
81
+
82
+
83
+ def run_microservice(module: str, port: int) -> Task:
84
+ name = to_kebab_case(module)
85
+ return CmdTask(
86
+ name=f"run-my-app-name-{name}",
87
+ description=f"🧩 Run My App Name {name.capitalize()}",
88
+ input=run_env_input,
89
+ env=[
90
+ EnvFile(path=os.path.join(APP_DIR, "template.env")),
91
+ EnvMap(
92
+ vars={
93
+ **MICROSERVICES_ENV_VARS,
94
+ }
95
+ ),
96
+ ],
97
+ cwd=APP_DIR,
98
+ cmd=[
99
+ ACTIVATE_VENV_SCRIPT,
100
+ set_env("MY_APP_NAME_MODULES", module),
101
+ set_env("MY_APP_NAME_PORT", f"{port}"),
102
+ run_my_app_name,
103
+ ],
104
+ render_cmd=False,
105
+ retries=2,
106
+ )
@@ -1,95 +1,15 @@
1
1
  import os
2
2
  import platform
3
3
 
4
- from my_app_name._zrb.config import (
5
- ACTIVATE_VENV_SCRIPT,
6
- APP_DIR,
7
- MICROSERVICES_ENV_VARS,
8
- MONOLITH_ENV_VARS,
9
- )
10
-
11
- from zrb import Cmd, CmdTask, EnvFile, EnvMap, StrInput, Task
12
- from zrb.util.string.conversion import double_quote, to_snake_case
13
-
14
-
15
- def create_migration(name: str, module: str) -> Task:
16
- return CmdTask(
17
- name=f"create-my-app-name-{name}-migration",
18
- description=f"🧩 Create My App Name {name.capitalize()} DB migration",
19
- input=StrInput(
20
- name="message",
21
- description="Migration message",
22
- prompt="Migration message",
23
- allow_empty=False,
24
- ),
25
- env=EnvFile(path=os.path.join(APP_DIR, "template.env")),
26
- cwd=APP_DIR,
27
- cmd=[
28
- ACTIVATE_VENV_SCRIPT,
29
- set_create_migration_db_url_env(module),
30
- set_module_env(module),
31
- cd_module_script(module),
32
- "alembic upgrade head",
33
- Cmd(
34
- "alembic revision --autogenerate -m {double_quote(ctx.input.message)}",
35
- auto_render=True,
36
- ),
37
- ],
38
- render_cmd=False,
39
- retries=2,
40
- )
41
-
4
+ from my_app_name._zrb.config import APP_DIR
42
5
 
43
- def migrate_module(name: str, module: str, as_microservices: bool) -> Task:
44
- env_vars = (
45
- dict(MICROSERVICES_ENV_VARS) if as_microservices else dict(MONOLITH_ENV_VARS)
46
- )
47
- if as_microservices:
48
- env_vars["MY_APP_NAME_MODULES"] = to_snake_case(module)
49
- return CmdTask(
50
- name=(
51
- f"migrate-my-app-name-{name}"
52
- if as_microservices
53
- else f"migrate-{name}-on-monolith"
54
- ),
55
- description=f"🧩 Run My App Name {name.capitalize()} DB migration",
56
- env=[
57
- EnvFile(path=os.path.join(APP_DIR, "template.env")),
58
- EnvMap(vars=env_vars),
59
- ],
60
- cwd=APP_DIR,
61
- cmd=[
62
- ACTIVATE_VENV_SCRIPT,
63
- cd_module_script(module),
64
- "alembic upgrade head",
65
- ],
66
- render_cmd=False,
67
- retries=2,
68
- )
6
+ from zrb import AnyContext
7
+ from zrb.util.string.conversion import double_quote, to_snake_case
69
8
 
70
9
 
71
- def run_microservice(name: str, port: int, module: str) -> Task:
72
- return CmdTask(
73
- name=f"run-my-app-name-{name}",
74
- description=f"🧩 Run My App Name {name.capitalize()}",
75
- env=[
76
- EnvFile(path=os.path.join(APP_DIR, "template.env")),
77
- EnvMap(
78
- vars={
79
- **MICROSERVICES_ENV_VARS,
80
- }
81
- ),
82
- ],
83
- cwd=APP_DIR,
84
- cmd=[
85
- ACTIVATE_VENV_SCRIPT,
86
- set_env("MY_APP_NAME_MODULES", module),
87
- set_env("MY_APP_NAME_PORT", f"{port}"),
88
- 'fastapi dev main.py --port "${MY_APP_NAME_PORT}"',
89
- ],
90
- render_cmd=False,
91
- retries=2,
92
- )
10
+ def run_my_app_name(ctx: AnyContext) -> str:
11
+ subcommand = "dev" if ctx.input.env == "dev" else "run"
12
+ return f'fastapi {subcommand} main.py --port "${{MY_APP_NAME_PORT}}"'
93
13
 
94
14
 
95
15
  def get_existing_module_names() -> list[str]:
@@ -122,9 +122,19 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
122
122
  return self._ensure_one(rows)
123
123
 
124
124
  async def get_by_ids(self, id_list: list[str]) -> list[ResponseModel]:
125
- return await self._select_to_response(
125
+ rows = await self._select_to_response(
126
126
  lambda q: q.where(self.db_model.id.in_(id_list))
127
127
  )
128
+ # raise error if any id not in id_list
129
+ existing_id_list = [row.id for row in rows]
130
+ inexist_id_list = [id for id in id_list if id not in existing_id_list]
131
+ if len(inexist_id_list) > 0:
132
+ raise NotFoundError(
133
+ f"{self.entity_name} not found, inexist ids: {', '.join(inexist_id_list)}"
134
+ )
135
+ # sort rows
136
+ row_dict = {row.id: row for row in rows}
137
+ return [row_dict[id] for id in id_list]
128
138
 
129
139
  async def count(self, filter: str | None = None) -> int:
130
140
  count_statement = select(func.count(1)).select_from(self.db_model)
@@ -184,21 +194,22 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
184
194
 
185
195
  async def create_bulk(self, data_list: list[CreateModel]) -> list[DBModel]:
186
196
  now = datetime.datetime.now(datetime.timezone.utc)
187
- data_dicts = [
197
+ data_dict_list = [
188
198
  self._model_to_data_dict(data, created_at=now, id=ulid.new().str)
189
199
  for data in data_list
190
200
  ]
201
+ id_list = [data_dict["id"] for data_dict in data_dict_list]
191
202
  async with self._session_scope() as session:
192
203
  await self._execute_statement(
193
- session, insert(self.db_model).values(data_dicts)
204
+ session, insert(self.db_model).values(data_dict_list)
194
205
  )
195
- id_list = [d["id"] for d in data_dicts]
196
206
  statement = select(self.db_model).where(self.db_model.id.in_(id_list))
197
207
  result = await self._execute_statement(session, statement)
198
- return [
199
- self.db_model(**entity.model_dump())
208
+ row_dict = {
209
+ entity.id: self.db_model(**entity.model_dump())
200
210
  for entity in result.scalars().all()
201
- ]
211
+ }
212
+ return [row_dict[id] for id in id_list]
202
213
 
203
214
  async def delete(self, id: str) -> DBModel:
204
215
  async with self._session_scope() as session:
@@ -220,11 +231,15 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
220
231
  await self._execute_statement(
221
232
  session, delete(self.db_model).where(self.db_model.id.in_(id_list))
222
233
  )
223
- return [self.db_model(**entity.model_dump()) for entity in entities]
234
+ row_dict = {
235
+ entity.id: self.db_model(**entity.model_dump()) for entity in entities
236
+ }
237
+ return [row_dict[id] for id in id_list]
224
238
 
225
239
  async def update(self, id: str, data: UpdateModel) -> DBModel:
226
240
  now = datetime.datetime.now(datetime.timezone.utc)
227
241
  update_data = self._model_to_data_dict(data, updated_at=now)
242
+ update_data = {k: v for k, v in update_data.items() if v is not None}
228
243
  async with self._session_scope() as session:
229
244
  statement = (
230
245
  update(self.db_model)
@@ -256,7 +271,8 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
256
271
  result = await self._execute_statement(
257
272
  session, select(self.db_model).where(self.db_model.id.in_(id_list))
258
273
  )
259
- return [
260
- self.db_model(**entity.model_dump())
274
+ row_dict = {
275
+ entity.id: self.db_model(**entity.model_dump())
261
276
  for entity in result.scalars().all()
262
- ]
277
+ }
278
+ return [row_dict[id] for id in id_list]