zrb 1.0.0b9__py3-none-any.whl → 1.1.0__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.
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +99 -55
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +301 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +131 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +128 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +297 -0
- 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
- 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
- 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
- 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
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +81 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/navigation_config_file.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +8 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/navigation_config_file.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +56 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +10 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +136 -52
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +3 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +19 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +46 -43
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +5 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +16 -21
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py +39 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +52 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/schema/navigation.py +95 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +277 -44
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +66 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +33 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +311 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +4 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/login.html +67 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/logout.html +49 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +160 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/style.css +14 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +94 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/pico-style.css +23 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +44 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/style.css +102 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +73 -18
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +6 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
- zrb/runner/web_route/refresh_token_api_route.py +1 -1
- zrb/runner/web_route/static/refresh-token.template.js +9 -0
- zrb/runner/web_route/static/static_route.py +1 -1
- zrb/task/base_task.py +10 -10
- zrb/util/codemod/modification_mode.py +3 -0
- zrb/util/codemod/modify_class.py +58 -0
- zrb/util/codemod/modify_class_parent.py +68 -0
- zrb/util/codemod/modify_class_property.py +128 -0
- zrb/util/codemod/modify_dict.py +75 -0
- zrb/util/codemod/modify_function.py +65 -0
- zrb/util/codemod/modify_function_call.py +68 -0
- zrb/util/codemod/modify_method.py +88 -0
- zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
- zrb/util/file.py +3 -2
- zrb/util/load.py +13 -7
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/METADATA +2 -2
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/RECORD +80 -46
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
- zrb/util/codemod/append_code_to_class.py +0 -35
- zrb/util/codemod/append_code_to_function.py +0 -38
- zrb/util/codemod/append_code_to_method.py +0 -55
- zrb/util/codemod/append_key_to_dict.py +0 -51
- zrb/util/codemod/append_param_to_function_call.py +0 -39
- zrb/util/codemod/prepend_parent_to_class.py +0 -38
- zrb/util/codemod/prepend_property_to_class.py +0 -55
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,17 @@
|
|
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
|
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
|
)
|
@@ -19,13 +25,42 @@ from my_app_name._zrb.task_util import (
|
|
19
25
|
)
|
20
26
|
from my_app_name._zrb.venv_task import prepare_venv
|
21
27
|
|
22
|
-
from zrb import CmdTask, EnvFile, EnvMap, Task
|
28
|
+
from zrb import AnyContext, CmdPath, CmdTask, EnvFile, EnvMap, Task, make_task
|
23
29
|
|
24
30
|
assert add_my_app_name_entity
|
25
31
|
assert add_my_app_name_module
|
26
32
|
assert add_my_app_name_column
|
27
33
|
assert format_my_app_name_code
|
28
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
|
+
|
29
64
|
# 🚀 Run/Migrate All ===========================================================
|
30
65
|
|
31
66
|
run_all = app_run_group.add_task(
|
@@ -107,40 +142,49 @@ migrate_microservices >> migrate_all
|
|
107
142
|
# 📡 Run/Migrate Gateway =======================================================
|
108
143
|
|
109
144
|
run_gateway = app_run_group.add_task(
|
110
|
-
run_microservice("gateway", 3001
|
145
|
+
run_microservice("gateway", 3001), alias="svc-gateway"
|
111
146
|
)
|
112
147
|
prepare_venv >> run_gateway >> run_microservices
|
113
148
|
|
114
149
|
create_gateway_migration = app_create_migration_group.add_task(
|
115
|
-
create_migration("gateway"
|
150
|
+
create_migration("gateway"), alias="gateway"
|
116
151
|
)
|
117
152
|
prepare_venv >> create_gateway_migration >> create_all_migration
|
118
153
|
|
119
|
-
migrate_monolith_gateway = migrate_module("gateway",
|
154
|
+
migrate_monolith_gateway = migrate_module("gateway", as_microservices=False)
|
120
155
|
prepare_venv >> migrate_monolith_gateway >> [migrate_monolith, run_monolith]
|
121
156
|
|
122
157
|
migrate_microservices_gateway = app_migrate_group.add_task(
|
123
|
-
migrate_module("gateway",
|
158
|
+
migrate_module("gateway", as_microservices=True),
|
124
159
|
alias="svc-gateway",
|
125
160
|
)
|
126
161
|
prepare_venv >> migrate_microservices_gateway >> [migrate_microservices, run_gateway]
|
127
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
|
+
|
128
169
|
# 🔐 Run/Migrate Auth ==========================================================
|
129
170
|
|
130
|
-
run_auth = app_run_group.add_task(
|
131
|
-
run_microservice("auth", 3002, "auth"), alias="svc-auth"
|
132
|
-
)
|
171
|
+
run_auth = app_run_group.add_task(run_microservice("auth", 3002), alias="svc-auth")
|
133
172
|
prepare_venv >> run_auth >> run_microservices
|
134
173
|
|
135
174
|
create_auth_migration = app_create_migration_group.add_task(
|
136
|
-
create_migration("auth"
|
175
|
+
create_migration("auth"), alias="auth"
|
137
176
|
)
|
138
177
|
prepare_venv >> create_auth_migration >> create_all_migration
|
139
178
|
|
140
|
-
migrate_monolith_auth = migrate_module("auth",
|
179
|
+
migrate_monolith_auth = migrate_module("auth", as_microservices=False)
|
141
180
|
prepare_venv >> migrate_monolith_auth >> [migrate_monolith, run_monolith]
|
142
181
|
|
143
182
|
migrate_microservices_auth = app_migrate_group.add_task(
|
144
|
-
migrate_module("auth",
|
183
|
+
migrate_module("auth", as_microservices=True), alias="svc-auth"
|
145
184
|
)
|
146
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
|
@@ -16,10 +16,11 @@ from my_app_name._zrb.util import (
|
|
16
16
|
)
|
17
17
|
|
18
18
|
from zrb import Cmd, CmdTask, EnvFile, EnvMap, StrInput, Task
|
19
|
-
from zrb.util.string.conversion import to_snake_case
|
19
|
+
from zrb.util.string.conversion import to_kebab_case, to_snake_case
|
20
20
|
|
21
21
|
|
22
|
-
def create_migration(
|
22
|
+
def create_migration(module: str) -> Task:
|
23
|
+
name = to_kebab_case(module)
|
23
24
|
return CmdTask(
|
24
25
|
name=f"create-my-app-name-{name}-migration",
|
25
26
|
description=f"🧩 Create My App Name {name.capitalize()} DB migration",
|
@@ -47,10 +48,14 @@ def create_migration(name: str, module: str) -> Task:
|
|
47
48
|
)
|
48
49
|
|
49
50
|
|
50
|
-
def migrate_module(
|
51
|
+
def migrate_module(
|
52
|
+
module: str, as_microservices: bool, additional_env_vars: dict[str, str] = {}
|
53
|
+
) -> Task:
|
54
|
+
name = to_kebab_case(module)
|
51
55
|
env_vars = (
|
52
56
|
dict(MICROSERVICES_ENV_VARS) if as_microservices else dict(MONOLITH_ENV_VARS)
|
53
57
|
)
|
58
|
+
env_vars.update(additional_env_vars)
|
54
59
|
if as_microservices:
|
55
60
|
env_vars["MY_APP_NAME_MODULES"] = to_snake_case(module)
|
56
61
|
return CmdTask(
|
@@ -75,7 +80,8 @@ def migrate_module(name: str, module: str, as_microservices: bool) -> Task:
|
|
75
80
|
)
|
76
81
|
|
77
82
|
|
78
|
-
def run_microservice(
|
83
|
+
def run_microservice(module: str, port: int) -> Task:
|
84
|
+
name = to_kebab_case(module)
|
79
85
|
return CmdTask(
|
80
86
|
name=f"run-my-app-name-{name}",
|
81
87
|
description=f"🧩 Run My App Name {name.capitalize()}",
|
@@ -2,10 +2,10 @@ import inspect
|
|
2
2
|
from enum import Enum
|
3
3
|
from functools import partial
|
4
4
|
from logging import Logger
|
5
|
-
from typing import Any, Callable,
|
5
|
+
from typing import Any, Callable, Union, get_args, get_origin
|
6
6
|
|
7
7
|
import httpx
|
8
|
-
from fastapi import APIRouter, Depends
|
8
|
+
from fastapi import APIRouter, Depends
|
9
9
|
from my_app_name.common.error import ClientAPIError
|
10
10
|
from pydantic import BaseModel
|
11
11
|
|
@@ -162,58 +162,20 @@ def _create_direct_client_method(logger: Logger, func: Callable, service: BaseSe
|
|
162
162
|
def _create_api_client_method(logger: Logger, route_param: RouteParam, base_url: str):
|
163
163
|
async def client_method(*args, **kwargs):
|
164
164
|
url = base_url + route_param.path
|
165
|
-
method = (
|
166
|
-
|
167
|
-
|
168
|
-
|
165
|
+
method = _get_api_client_method(route_param)
|
166
|
+
body_param_names = _get_api_client_body_param_names(route_param, method)
|
167
|
+
path_params, query_params, body_params = _create_api_client_request_params(
|
168
|
+
route_param, body_param_names, args, kwargs
|
169
169
|
)
|
170
|
-
# Get the signature of the original function
|
171
|
-
sig = inspect.signature(route_param.func)
|
172
|
-
# Bind the arguments to the signature
|
173
|
-
bound_args = sig.bind(*args, **kwargs)
|
174
|
-
bound_args.apply_defaults()
|
175
|
-
# Analyze parameters
|
176
|
-
function_params = list(sig.parameters.values())
|
177
|
-
body_param_names = [
|
178
|
-
p.name
|
179
|
-
for p in function_params
|
180
|
-
if (
|
181
|
-
p.name != "self"
|
182
|
-
and f"{{{p.name}}}" not in route_param.path
|
183
|
-
and p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
184
|
-
and (
|
185
|
-
method not in ["get", "delete"]
|
186
|
-
or (method == "delete" and p.annotation not in [str, float, bool])
|
187
|
-
)
|
188
|
-
)
|
189
|
-
]
|
190
|
-
# Prepare the request
|
191
|
-
path_params = {}
|
192
|
-
query_params = {}
|
193
|
-
body_params = {}
|
194
|
-
for name, value in bound_args.arguments.items():
|
195
|
-
if name == "self":
|
196
|
-
continue
|
197
|
-
if f"{{{name}}}" in route_param.path:
|
198
|
-
path_params[name] = value
|
199
|
-
elif name not in body_param_names:
|
200
|
-
query_params[name] = _parse_api_param(value)
|
201
|
-
elif len(body_param_names) == 1 and name == body_param_names[0]:
|
202
|
-
# If there's only one body parameter, use its value directly
|
203
|
-
body_params = _parse_api_param(value)
|
204
|
-
else:
|
205
|
-
body_params[name] = _parse_api_param(value)
|
206
170
|
# Format the URL with path parameters
|
207
171
|
url = url.format(**path_params)
|
172
|
+
json_body_params = None if method == "get" else body_params
|
208
173
|
logger.info(
|
209
|
-
f"Sending request to {url} with method {method}, json={
|
174
|
+
f"Sending request to {url} with method {method}, json={json_body_params}, params={query_params}" # noqa
|
210
175
|
)
|
211
176
|
async with httpx.AsyncClient() as client:
|
212
177
|
response = await client.request(
|
213
|
-
method=method,
|
214
|
-
url=url,
|
215
|
-
params=query_params,
|
216
|
-
json=None if method == "get" else body_params,
|
178
|
+
method=method, url=url, params=query_params, json=json_body_params
|
217
179
|
)
|
218
180
|
logger.info(
|
219
181
|
f"Received response: status={response.status_code}, content={response.content}"
|
@@ -225,17 +187,139 @@ def _create_api_client_method(logger: Logger, route_param: RouteParam, base_url:
|
|
225
187
|
else response.text
|
226
188
|
)
|
227
189
|
raise ClientAPIError(response.status_code, error_detail)
|
228
|
-
return response
|
190
|
+
return _parse_api_client_response(logger, route_param, response)
|
229
191
|
|
230
192
|
return client_method
|
231
193
|
|
232
194
|
|
233
|
-
def
|
195
|
+
def _parse_api_client_response(
|
196
|
+
logger: Logger, route_param: RouteParam, response: Any
|
197
|
+
) -> Any:
|
198
|
+
sig = inspect.signature(route_param.func)
|
199
|
+
try:
|
200
|
+
response_data = response.json()
|
201
|
+
except Exception:
|
202
|
+
logger.warning("Failed to parse JSON")
|
203
|
+
return None
|
204
|
+
return_annotation = sig.return_annotation # e.g., list[User]
|
205
|
+
if return_annotation is inspect.Signature.empty:
|
206
|
+
logger.warning("No return annotation detected, return value as is")
|
207
|
+
return response_data # No return type specified, return raw JSON
|
208
|
+
origin = get_origin(return_annotation) # e.g., list
|
209
|
+
args = get_args(return_annotation) # e.g., (User,)
|
210
|
+
try:
|
211
|
+
if origin is None: # Not a generic type, so check it directly
|
212
|
+
if inspect.isclass(return_annotation) and issubclass(
|
213
|
+
return_annotation, BaseModel
|
214
|
+
):
|
215
|
+
if response_data:
|
216
|
+
return return_annotation.model_validate(response_data)
|
217
|
+
return None
|
218
|
+
elif origin in {list, set, tuple} and args:
|
219
|
+
model_type = args[0]
|
220
|
+
if inspect.isclass(model_type) and issubclass(model_type, BaseModel):
|
221
|
+
if isinstance(response_data, list):
|
222
|
+
return [model_type.model_validate(item) for item in response_data]
|
223
|
+
elif isinstance(response_data, tuple):
|
224
|
+
return tuple(
|
225
|
+
model_type.model_validate(item) for item in response_data
|
226
|
+
)
|
227
|
+
elif isinstance(response_data, set):
|
228
|
+
return {model_type.model_validate(item) for item in response_data}
|
229
|
+
return None
|
230
|
+
elif origin is Union and len(args) == 2 and type(None) in args:
|
231
|
+
model_type = next(
|
232
|
+
(
|
233
|
+
arg
|
234
|
+
for arg in args
|
235
|
+
if inspect.isclass(arg) and issubclass(arg, BaseModel)
|
236
|
+
),
|
237
|
+
None,
|
238
|
+
)
|
239
|
+
if response_data and model_type:
|
240
|
+
return model_type.model_validate(response_data)
|
241
|
+
return None
|
242
|
+
elif origin is dict and len(args) == 2:
|
243
|
+
key_type, value_type = args
|
244
|
+
if inspect.isclass(value_type) and issubclass(value_type, BaseModel):
|
245
|
+
if inspect(response_data, dict):
|
246
|
+
return {
|
247
|
+
k: value_type.model_validate(v)
|
248
|
+
for k, v in response_data.items()
|
249
|
+
}
|
250
|
+
return None
|
251
|
+
return response_data
|
252
|
+
except Exception:
|
253
|
+
logger.warning(
|
254
|
+
"Return annotation detected, but parsing error, return value as is"
|
255
|
+
)
|
256
|
+
return response_data
|
257
|
+
|
258
|
+
|
259
|
+
def _create_api_client_request_params(
|
260
|
+
route_param: RouteParam,
|
261
|
+
body_param_names: list[str],
|
262
|
+
args: list[Any],
|
263
|
+
kwargs: dict[str, Any],
|
264
|
+
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
265
|
+
# Get the signature of the original function
|
266
|
+
sig = inspect.signature(route_param.func)
|
267
|
+
# Bind the arguments to the signature
|
268
|
+
bound_args = sig.bind(*args, **kwargs)
|
269
|
+
bound_args.apply_defaults()
|
270
|
+
# Prepare the request
|
271
|
+
path_params = {}
|
272
|
+
query_params = {}
|
273
|
+
body_params = {}
|
274
|
+
for name, value in bound_args.arguments.items():
|
275
|
+
if name == "self":
|
276
|
+
continue
|
277
|
+
if f"{{{name}}}" in route_param.path:
|
278
|
+
path_params[name] = value
|
279
|
+
elif name not in body_param_names:
|
280
|
+
query_params[name] = _parse_api_client_param(value)
|
281
|
+
elif len(body_param_names) == 1 and name == body_param_names[0]:
|
282
|
+
# If there's only one body parameter, use its value directly
|
283
|
+
body_params = _parse_api_client_param(value)
|
284
|
+
else:
|
285
|
+
body_params[name] = _parse_api_client_param(value)
|
286
|
+
return path_params, query_params, body_params
|
287
|
+
|
288
|
+
|
289
|
+
def _parse_api_client_param(data: Any) -> Any:
|
234
290
|
if isinstance(data, BaseModel):
|
235
291
|
return data.model_dump()
|
236
|
-
elif isinstance(data, list):
|
237
|
-
return [_parse_api_param(item) for item in data]
|
238
292
|
elif isinstance(data, dict):
|
239
|
-
return {key:
|
293
|
+
return {key: _parse_api_client_param(value) for key, value in data.items()}
|
294
|
+
elif isinstance(data, list):
|
295
|
+
return [_parse_api_client_param(item) for item in data]
|
296
|
+
elif isinstance(data, tuple):
|
297
|
+
return tuple(_parse_api_client_param(item) for item in data)
|
298
|
+
elif isinstance(data, set):
|
299
|
+
return {_parse_api_client_param(item) for item in data}
|
240
300
|
else:
|
241
301
|
return data
|
302
|
+
|
303
|
+
|
304
|
+
def _get_api_client_method(route_param: RouteParam) -> str:
|
305
|
+
if isinstance(route_param.methods, list):
|
306
|
+
return route_param.methods[0].lower()
|
307
|
+
return route_param.methods.lower()
|
308
|
+
|
309
|
+
|
310
|
+
def _get_api_client_body_param_names(route_param: RouteParam, method: str):
|
311
|
+
sig = inspect.signature(route_param.func)
|
312
|
+
function_params = list(sig.parameters.values())
|
313
|
+
return [
|
314
|
+
p.name
|
315
|
+
for p in function_params
|
316
|
+
if (
|
317
|
+
p.name != "self"
|
318
|
+
and f"{{{p.name}}}" not in route_param.path
|
319
|
+
and p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
320
|
+
and (
|
321
|
+
method not in ["get", "delete"]
|
322
|
+
or (method == "delete" and p.annotation not in [str, float, bool])
|
323
|
+
)
|
324
|
+
)
|
325
|
+
]
|
@@ -45,7 +45,7 @@ def create_default_filter_param_parser() -> (
|
|
45
45
|
Callable[[SQLModel, str], list[ClauseElement]]
|
46
46
|
):
|
47
47
|
def parse_filter_param(model: SQLModel, query: str) -> list[ClauseElement]:
|
48
|
-
"""
|
48
|
+
r"""
|
49
49
|
Parse the filter parameter and return a list of SQLAlchemy ClauseElement objects.
|
50
50
|
|
51
51
|
Args:
|
@@ -72,7 +72,7 @@ def create_default_filter_param_parser() -> (
|
|
72
72
|
# Returns [UserModel.age >= 18, UserModel.name.like("John%"), UserModel.role.in_(["admin", "user"]), UserModel.address == "123, Main St."]
|
73
73
|
"""
|
74
74
|
filters: list[ClauseElement] = []
|
75
|
-
filter_parts =
|
75
|
+
filter_parts = split_unescaped(query)
|
76
76
|
for part in filter_parts:
|
77
77
|
match = re.match(r"(.+):(.+):(.+)", part)
|
78
78
|
if match:
|
@@ -101,5 +101,5 @@ def create_default_filter_param_parser() -> (
|
|
101
101
|
return parse_filter_param
|
102
102
|
|
103
103
|
|
104
|
-
def
|
104
|
+
def split_unescaped(s: str, delimiter: str = ",") -> list[str]:
|
105
105
|
return re.split(r"(?<!\\)" + re.escape(delimiter), s)
|
@@ -27,7 +27,7 @@ def render_str(template_path: str, **data: Any) -> str:
|
|
27
27
|
def render_page(
|
28
28
|
template_path: str,
|
29
29
|
status_code: int = 200,
|
30
|
-
headers: dict[str, str] = None,
|
30
|
+
headers: dict[str, str] | None = None,
|
31
31
|
media_type: str | None = None,
|
32
32
|
**data: Any
|
33
33
|
) -> HTMLResponse:
|
@@ -8,25 +8,35 @@ APP_VERSION = "0.1.0"
|
|
8
8
|
|
9
9
|
APP_GATEWAY_VIEW_PATH = os.path.join(APP_PATH, "module", "gateway", "view")
|
10
10
|
APP_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH = os.getenv(
|
11
|
-
"
|
11
|
+
"MY_APP_NAME_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH",
|
12
12
|
os.path.join("template", "default.html"),
|
13
13
|
)
|
14
|
-
|
14
|
+
APP_GATEWAY_PICO_CSS_COLOR = os.getenv("MY_APP_NAME_GATEWAY_PICO_CSS_COLOR", "")
|
15
|
+
APP_GATEWAY_PICO_CSS_PATH = (
|
16
|
+
"/static/pico-css/pico.min.css"
|
17
|
+
if APP_GATEWAY_PICO_CSS_COLOR == ""
|
18
|
+
else f"/static/pico-css/pico.{APP_GATEWAY_PICO_CSS_COLOR}.min.css"
|
19
|
+
)
|
15
20
|
APP_GATEWAY_CSS_PATH_LIST = [
|
16
21
|
path
|
17
|
-
for path in os.getenv("
|
22
|
+
for path in os.getenv("MY_APP_NAME_GATEWAY_CSS_PATH", "").split(":")
|
18
23
|
if path != ""
|
19
24
|
]
|
20
25
|
APP_GATEWAY_JS_PATH_LIST = [
|
21
|
-
path
|
26
|
+
path
|
27
|
+
for path in os.getenv("MY_APP_NAME_GATEWAY_JS_PATH", "").split(":")
|
28
|
+
if path != ""
|
22
29
|
]
|
23
|
-
APP_GATEWAY_TITLE = os.getenv("
|
24
|
-
APP_GATEWAY_SUBTITLE = os.getenv("
|
30
|
+
APP_GATEWAY_TITLE = os.getenv("MY_APP_NAME_GATEWAY_TITLE", "My App Name")
|
31
|
+
APP_GATEWAY_SUBTITLE = os.getenv("MY_APP_NAME_GATEWAY_SUBTITLE", "Just Another App")
|
25
32
|
APP_GATEWAY_LOGO_PATH = os.getenv(
|
26
|
-
"
|
33
|
+
"MY_APP_NAME_GATEWAY_LOGO", "/static/images/android-chrome-192x192.png"
|
34
|
+
)
|
35
|
+
APP_GATEWAY_FOOTER = os.getenv(
|
36
|
+
"MY_APP_NAME_GATEWAY_FOOTER", f"{APP_GATEWAY_TITLE} © 2025"
|
27
37
|
)
|
28
38
|
APP_GATEWAY_FAVICON_PATH = os.getenv(
|
29
|
-
"
|
39
|
+
"MY_APP_NAME_GATEWAY_FAVICON", "/static/images/favicon-32x32.png"
|
30
40
|
)
|
31
41
|
|
32
42
|
APP_MODE = os.getenv("MY_APP_NAME_MODE", "monolith")
|
@@ -80,4 +90,5 @@ APP_AUTH_SECRET_KEY = os.getenv("MY_APP_NAME_AUTH_SECRET_KEY", "my-secret-key")
|
|
80
90
|
APP_AUTH_PRIORITIZE_NEW_SESSION = (
|
81
91
|
os.getenv("MY_APP_NAME_AUTH_PRIORITIZE_NEW_SESSION", "1").lower() in TRUE_STRS
|
82
92
|
)
|
93
|
+
|
83
94
|
APP_AUTH_BASE_URL = os.getenv("MY_APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
|
@@ -32,18 +32,18 @@ def upgrade() -> None:
|
|
32
32
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
33
33
|
sa.PrimaryKeyConstraint("id"),
|
34
34
|
)
|
35
|
-
op.create_index(op.f("
|
35
|
+
op.create_index(op.f("ix_permissions_name"), "permissions", ["name"], unique=True)
|
36
36
|
op.create_index(
|
37
|
-
op.f("
|
37
|
+
op.f("ix_permissions_created_at"), "permissions", ["created_at"], unique=False
|
38
38
|
)
|
39
39
|
op.create_index(
|
40
|
-
op.f("
|
40
|
+
op.f("ix_permissions_created_by"), "permissions", ["created_by"], unique=False
|
41
41
|
)
|
42
42
|
op.create_index(
|
43
|
-
op.f("
|
43
|
+
op.f("ix_permissions_updated_at"), "permissions", ["updated_at"], unique=False
|
44
44
|
)
|
45
45
|
op.create_index(
|
46
|
-
op.f("
|
46
|
+
op.f("ix_permissions_updated_by"), "permissions", ["updated_by"], unique=False
|
47
47
|
)
|
48
48
|
|
49
49
|
op.create_table(
|
@@ -57,11 +57,11 @@ def upgrade() -> None:
|
|
57
57
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
58
58
|
sa.PrimaryKeyConstraint("id"),
|
59
59
|
)
|
60
|
-
op.create_index(op.f("
|
61
|
-
op.create_index(op.f("
|
62
|
-
op.create_index(op.f("
|
63
|
-
op.create_index(op.f("
|
64
|
-
op.create_index(op.f("
|
60
|
+
op.create_index(op.f("ix_roles_name"), "roles", ["name"], unique=True)
|
61
|
+
op.create_index(op.f("ix_roles_created_at"), "roles", ["created_at"], unique=False)
|
62
|
+
op.create_index(op.f("ix_roles_created_by"), "roles", ["created_by"], unique=False)
|
63
|
+
op.create_index(op.f("ix_roles_updated_at"), "roles", ["updated_at"], unique=False)
|
64
|
+
op.create_index(op.f("ix_roles_updated_by"), "roles", ["updated_by"], unique=False)
|
65
65
|
|
66
66
|
op.create_table(
|
67
67
|
"role_permissions",
|
@@ -97,12 +97,12 @@ def upgrade() -> None:
|
|
97
97
|
sa.Column("updated_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
98
98
|
sa.PrimaryKeyConstraint("id"),
|
99
99
|
)
|
100
|
-
op.create_index(op.f("
|
101
|
-
op.create_index(op.f("
|
102
|
-
op.create_index(op.f("
|
103
|
-
op.create_index(op.f("
|
104
|
-
op.create_index(op.f("
|
105
|
-
op.create_index(op.f("
|
100
|
+
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
|
101
|
+
op.create_index(op.f("ix_users_active"), "users", ["active"], unique=False)
|
102
|
+
op.create_index(op.f("ix_users_created_at"), "users", ["created_at"], unique=False)
|
103
|
+
op.create_index(op.f("ix_users_created_by"), "users", ["created_by"], unique=False)
|
104
|
+
op.create_index(op.f("ix_users_updated_at"), "users", ["updated_at"], unique=False)
|
105
|
+
op.create_index(op.f("ix_users_updated_by"), "users", ["updated_by"], unique=False)
|
106
106
|
|
107
107
|
op.create_table(
|
108
108
|
"user_roles",
|
@@ -131,24 +131,27 @@ def upgrade() -> None:
|
|
131
131
|
sa.PrimaryKeyConstraint("id"),
|
132
132
|
)
|
133
133
|
op.create_index(
|
134
|
-
op.f("
|
134
|
+
op.f("ix_user_sessions_user_id"), "user_sessions", ["user_id"], unique=False
|
135
135
|
)
|
136
136
|
op.create_index(
|
137
|
-
op.f("
|
137
|
+
op.f("ix_user_sessions_access_token"),
|
138
|
+
"user_sessions",
|
139
|
+
["access_token"],
|
140
|
+
unique=False,
|
138
141
|
)
|
139
142
|
op.create_index(
|
140
|
-
op.f("
|
143
|
+
op.f("ix_user_sessions_refresh_token"),
|
141
144
|
"user_sessions",
|
142
145
|
["refresh_token"],
|
143
|
-
unique=
|
146
|
+
unique=False,
|
144
147
|
)
|
145
148
|
op.create_index(
|
146
|
-
op.f("
|
149
|
+
op.f("ix_user_sessions_access_token_expired_at"),
|
147
150
|
"user_sessions",
|
148
151
|
["access_token_expired_at"],
|
149
152
|
)
|
150
153
|
op.create_index(
|
151
|
-
op.f("
|
154
|
+
op.f("ix_user_sessions_refresh_token_expired_at"),
|
152
155
|
"user_sessions",
|
153
156
|
["refresh_token_expired_at"],
|
154
157
|
)
|
@@ -157,14 +160,14 @@ def upgrade() -> None:
|
|
157
160
|
|
158
161
|
def downgrade() -> None:
|
159
162
|
# ### commands auto generated by Alembic - please adjust! ###
|
160
|
-
op.drop_index(op.f("
|
161
|
-
op.drop_index(op.f("
|
162
|
-
op.drop_index(op.f("
|
163
|
+
op.drop_index(op.f("ix_user_sessions_user_id"), table_name="user_sessions")
|
164
|
+
op.drop_index(op.f("ix_user_sessions_access_token"), table_name="user_sessions")
|
165
|
+
op.drop_index(op.f("ix_user_sessions_refresh_token"), table_name="user_sessions")
|
163
166
|
op.drop_index(
|
164
|
-
op.f("
|
167
|
+
op.f("ix_user_sessions_access_token_expired_at"), table_name="user_sessions"
|
165
168
|
)
|
166
169
|
op.drop_index(
|
167
|
-
op.f("
|
170
|
+
op.f("ix_user_sessions_refresh_token_expired_at"), table_name="user_sessions"
|
168
171
|
)
|
169
172
|
op.drop_table("user_sessions")
|
170
173
|
|
@@ -172,12 +175,12 @@ def downgrade() -> None:
|
|
172
175
|
op.drop_index(op.f("ix_user_roles_role_id"), table_name="user_roles")
|
173
176
|
op.drop_table("user_roles")
|
174
177
|
|
175
|
-
op.drop_index(op.f("
|
176
|
-
op.drop_index(op.f("
|
177
|
-
op.drop_index(op.f("
|
178
|
-
op.drop_index(op.f("
|
179
|
-
op.drop_index(op.f("
|
180
|
-
op.drop_index(op.f("
|
178
|
+
op.drop_index(op.f("ix_users_username"), table_name="users")
|
179
|
+
op.drop_index(op.f("ix_users_active"), table_name="users")
|
180
|
+
op.drop_index(op.f("ix_users_updated_by"), table_name="users")
|
181
|
+
op.drop_index(op.f("ix_users_updated_at"), table_name="users")
|
182
|
+
op.drop_index(op.f("ix_users_created_by"), table_name="users")
|
183
|
+
op.drop_index(op.f("ix_users_created_at"), table_name="users")
|
181
184
|
op.drop_table("users")
|
182
185
|
|
183
186
|
op.drop_index(op.f("ix_role_permissions_role_id"), table_name="role_permissions")
|
@@ -186,17 +189,17 @@ def downgrade() -> None:
|
|
186
189
|
)
|
187
190
|
op.drop_table("role_permissions")
|
188
191
|
|
189
|
-
op.drop_index(op.f("
|
190
|
-
op.drop_index(op.f("
|
191
|
-
op.drop_index(op.f("
|
192
|
-
op.drop_index(op.f("
|
193
|
-
op.drop_index(op.f("
|
192
|
+
op.drop_index(op.f("ix_roles_name"), table_name="roles")
|
193
|
+
op.drop_index(op.f("ix_roles_updated_by"), table_name="roles")
|
194
|
+
op.drop_index(op.f("ix_roles_updated_at"), table_name="roles")
|
195
|
+
op.drop_index(op.f("ix_roles_created_by"), table_name="roles")
|
196
|
+
op.drop_index(op.f("ix_roles_created_at"), table_name="roles")
|
194
197
|
op.drop_table("roles")
|
195
198
|
|
196
|
-
op.drop_index(op.f("
|
197
|
-
op.drop_index(op.f("
|
198
|
-
op.drop_index(op.f("
|
199
|
-
op.drop_index(op.f("
|
200
|
-
op.drop_index(op.f("
|
199
|
+
op.drop_index(op.f("ix_permissions_updated_by"), table_name="permissions")
|
200
|
+
op.drop_index(op.f("ix_permissions_updated_at"), table_name="permissions")
|
201
|
+
op.drop_index(op.f("ix_permissions_created_by"), table_name="permissions")
|
202
|
+
op.drop_index(op.f("ix_permissions_created_at"), table_name="permissions")
|
203
|
+
op.drop_index(op.f("ix_permissions_name"), table_name="permissions")
|
201
204
|
op.drop_table("permissions")
|
202
205
|
# ### end Alembic commands ###
|