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.
- zrb/__main__.py +3 -0
- zrb/builtin/project/add/fastapp/fastapp_task.py +1 -0
- 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 +4 -4
- 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 +108 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +67 -4
- 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
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +1 -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 +57 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +6 -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/task.py +65 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +106 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +6 -86
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +27 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +140 -51
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +22 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +21 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +106 -61
- 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/migration_metadata.py +3 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +15 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +4 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +24 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +14 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +134 -97
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +28 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +215 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +30 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +216 -41
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +57 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +7 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +2 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +13 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +64 -12
- 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 +17 -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/task/base_task.py +10 -10
- zrb/task/cmd_task.py +2 -5
- zrb/util/cmd/command.py +39 -48
- 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-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/METADATA +2 -1
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/RECORD +72 -55
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +0 -48
- 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.0b8.dist-info → zrb-1.0.0b10.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/entry_points.txt +0 -0
@@ -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
|
|
@@ -56,7 +56,6 @@ class BaseService:
|
|
56
56
|
response_model: Any = None,
|
57
57
|
status_code: int | None = None,
|
58
58
|
tags: list[str | Enum] | None = None,
|
59
|
-
dependencies: Sequence[params.Depends] | None = None,
|
60
59
|
summary: str | None = None,
|
61
60
|
description: str = None,
|
62
61
|
deprecated: bool | None = None,
|
@@ -160,56 +159,24 @@ def _create_direct_client_method(logger: Logger, func: Callable, service: BaseSe
|
|
160
159
|
return client_method
|
161
160
|
|
162
161
|
|
163
|
-
def _create_api_client_method(logger: Logger,
|
162
|
+
def _create_api_client_method(logger: Logger, route_param: RouteParam, base_url: str):
|
164
163
|
async def client_method(*args, **kwargs):
|
165
|
-
url = base_url +
|
166
|
-
method = (
|
167
|
-
|
168
|
-
|
169
|
-
|
164
|
+
url = base_url + route_param.path
|
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
|
170
169
|
)
|
171
|
-
# Get the signature of the original function
|
172
|
-
sig = inspect.signature(param.func)
|
173
|
-
# Bind the arguments to the signature
|
174
|
-
bound_args = sig.bind(*args, **kwargs)
|
175
|
-
bound_args.apply_defaults()
|
176
|
-
# Analyze parameters
|
177
|
-
params = list(sig.parameters.values())
|
178
|
-
body_params = [
|
179
|
-
p
|
180
|
-
for p in params
|
181
|
-
if p.name != "self" and p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
182
|
-
]
|
183
|
-
# Prepare the request
|
184
|
-
path_params = {}
|
185
|
-
query_params = {}
|
186
|
-
body = {}
|
187
|
-
for name, value in bound_args.arguments.items():
|
188
|
-
if name == "self":
|
189
|
-
continue
|
190
|
-
if f"{{{name}}}" in param.path:
|
191
|
-
path_params[name] = value
|
192
|
-
elif isinstance(value, BaseModel):
|
193
|
-
body = _parse_api_param(value)
|
194
|
-
elif method in ["get", "delete"]:
|
195
|
-
query_params[name] = _parse_api_param(value)
|
196
|
-
elif len(body_params) == 1 and name == body_params[0].name:
|
197
|
-
# If there's only one body parameter, use its value directly
|
198
|
-
body = _parse_api_param(value)
|
199
|
-
else:
|
200
|
-
body[name] = _parse_api_param(value)
|
201
170
|
# Format the URL with path parameters
|
202
171
|
url = url.format(**path_params)
|
172
|
+
json_body_params = None if method == "get" else body_params
|
203
173
|
logger.info(
|
204
|
-
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
|
205
175
|
)
|
206
176
|
async with httpx.AsyncClient() as client:
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
response = await getattr(client, method)(
|
211
|
-
url, json=body, params=query_params
|
212
|
-
)
|
177
|
+
response = await client.request(
|
178
|
+
method=method, url=url, params=query_params, json=json_body_params
|
179
|
+
)
|
213
180
|
logger.info(
|
214
181
|
f"Received response: status={response.status_code}, content={response.content}"
|
215
182
|
)
|
@@ -220,17 +187,139 @@ def _create_api_client_method(logger: Logger, param: RouteParam, base_url: str):
|
|
220
187
|
else response.text
|
221
188
|
)
|
222
189
|
raise ClientAPIError(response.status_code, error_detail)
|
223
|
-
return response
|
190
|
+
return _parse_api_client_response(logger, route_param, response)
|
224
191
|
|
225
192
|
return client_method
|
226
193
|
|
227
194
|
|
228
|
-
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:
|
229
290
|
if isinstance(data, BaseModel):
|
230
291
|
return data.model_dump()
|
231
|
-
elif isinstance(data, list):
|
232
|
-
return [_parse_api_param(item) for item in data]
|
233
292
|
elif isinstance(data, dict):
|
234
|
-
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}
|
235
300
|
else:
|
236
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
|
+
]
|
@@ -8,11 +8,26 @@ class NotFoundError(HTTPException):
|
|
8
8
|
super().__init__(404, {"message": message}, headers)
|
9
9
|
|
10
10
|
|
11
|
+
class ForbiddenError(HTTPException):
|
12
|
+
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
13
|
+
super().__init__(403, {"message": message}, headers)
|
14
|
+
|
15
|
+
|
16
|
+
class UnauthorizedError(HTTPException):
|
17
|
+
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
18
|
+
super().__init__(401, {"message": message}, headers)
|
19
|
+
|
20
|
+
|
11
21
|
class InvalidValueError(HTTPException):
|
12
22
|
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
13
23
|
super().__init__(422, {"message": message}, headers)
|
14
24
|
|
15
25
|
|
26
|
+
class InternalServerError(HTTPException):
|
27
|
+
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
28
|
+
super().__init__(500, {"message": message}, headers)
|
29
|
+
|
30
|
+
|
16
31
|
class ClientAPIError(HTTPException):
|
17
32
|
def __init__(
|
18
33
|
self, status_code: int, message: str, headers: Dict[str, str] | None = None
|
@@ -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:
|
@@ -1,5 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
+
TRUE_STRS = ["true", "1", "yes", "y", "active", "on"]
|
4
|
+
FALSE_STRS = ["false", "0", "no", "n", "inactive", "off"]
|
5
|
+
|
3
6
|
APP_PATH = os.path.dirname(__file__)
|
4
7
|
APP_VERSION = "0.1.0"
|
5
8
|
|
@@ -51,16 +54,31 @@ APP_AUTH_SUPER_USER_PASSWORD = os.getenv(
|
|
51
54
|
"MY_APP_NAME_AUTH_SUPER_USER_PASSWORD", "my-secure-password"
|
52
55
|
)
|
53
56
|
APP_AUTH_GUEST_USER = os.getenv("MY_APP_NAME_AUTH_GUEST_USER", "user")
|
54
|
-
APP_AUTH_GUEST_USER_PERMISSIONS =
|
57
|
+
APP_AUTH_GUEST_USER_PERMISSIONS = [
|
55
58
|
permission_name.strip()
|
56
59
|
for permission_name in os.getenv(
|
57
60
|
"MY_APP_NAME_AUTH_GUEST_USER_PERMISSIONS", ""
|
58
61
|
).split(",")
|
59
62
|
if permission_name.strip() != ""
|
63
|
+
]
|
64
|
+
APP_AUTH_MAX_PARALLEL_SESSION = int(
|
65
|
+
os.getenv("MY_APP_NAME_AUTH_MAX_PARALLEL_SESSION", "1")
|
66
|
+
)
|
67
|
+
APP_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES = int(
|
68
|
+
os.getenv("MY_APP_NAME_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES", "30")
|
69
|
+
)
|
70
|
+
APP_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES = int(
|
71
|
+
os.getenv("MY_APP_NAME_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES", "1440")
|
72
|
+
)
|
73
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME = os.getenv(
|
74
|
+
"MY_APP_NAME_AUTH_ACCESS_TOKEN_COOKIE_NAME", "access_token"
|
75
|
+
)
|
76
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME = os.getenv(
|
77
|
+
"MY_APP_NAME_AUTH_REFRESH_TOKEN_COOKIE_NAME", "refresh_token"
|
60
78
|
)
|
61
|
-
|
62
|
-
|
63
|
-
os.getenv("
|
79
|
+
APP_AUTH_SECRET_KEY = os.getenv("MY_APP_NAME_AUTH_SECRET_KEY", "my-secret-key")
|
80
|
+
APP_AUTH_PRIORITIZE_NEW_SESSION = (
|
81
|
+
os.getenv("MY_APP_NAME_AUTH_PRIORITIZE_NEW_SESSION", "1").lower() in TRUE_STRS
|
64
82
|
)
|
65
83
|
|
66
84
|
APP_AUTH_BASE_URL = os.getenv("MY_APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
|
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py
CHANGED
@@ -13,15 +13,36 @@ from my_app_name.schema.role import (
|
|
13
13
|
RoleUpdateWithPermissionsAndAudit,
|
14
14
|
)
|
15
15
|
from my_app_name.schema.user import (
|
16
|
+
AuthUserResponse,
|
16
17
|
MultipleUserResponse,
|
17
18
|
UserCreateWithRolesAndAudit,
|
19
|
+
UserCredentials,
|
18
20
|
UserResponse,
|
21
|
+
UserSessionResponse,
|
19
22
|
UserUpdateWithRolesAndAudit,
|
20
23
|
)
|
21
24
|
|
22
25
|
|
23
26
|
class AuthClient(ABC):
|
24
27
|
|
28
|
+
@abstractmethod
|
29
|
+
async def get_current_user(self, access_token: str) -> AuthUserResponse:
|
30
|
+
"""Get current user based on access token"""
|
31
|
+
|
32
|
+
@abstractmethod
|
33
|
+
async def create_user_session(
|
34
|
+
self, credential: UserCredentials
|
35
|
+
) -> UserSessionResponse:
|
36
|
+
"""Create new user session"""
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
async def update_user_session(self, refresh_token: str) -> UserSessionResponse:
|
40
|
+
"""Update user session"""
|
41
|
+
|
42
|
+
@abstractmethod
|
43
|
+
async def delete_user_session(self, refresh_token: str) -> UserSessionResponse:
|
44
|
+
"""Delete user session"""
|
45
|
+
|
25
46
|
# Permission related methods
|
26
47
|
|
27
48
|
@abstractmethod
|
@@ -22,7 +22,7 @@ depends_on: Union[str, Sequence[str], None] = None
|
|
22
22
|
def upgrade() -> None:
|
23
23
|
# ### commands auto generated by Alembic - please adjust! ###
|
24
24
|
op.create_table(
|
25
|
-
"
|
25
|
+
"permissions",
|
26
26
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
27
27
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
28
28
|
sa.Column("created_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
@@ -32,22 +32,22 @@ 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(
|
50
|
-
"
|
50
|
+
"roles",
|
51
51
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
52
52
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
53
53
|
sa.Column("created_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
@@ -57,14 +57,14 @@ 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",
|
68
68
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
69
69
|
sa.Column("role_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
70
70
|
sa.Column("permission_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
@@ -73,34 +73,39 @@ def upgrade() -> None:
|
|
73
73
|
sa.PrimaryKeyConstraint("id"),
|
74
74
|
)
|
75
75
|
op.create_index(
|
76
|
-
op.f("
|
77
|
-
"
|
76
|
+
op.f("ix_role_permissions_permission_id"),
|
77
|
+
"role_permissions",
|
78
78
|
["permission_id"],
|
79
79
|
unique=False,
|
80
80
|
)
|
81
81
|
op.create_index(
|
82
|
-
op.f("
|
82
|
+
op.f("ix_role_permissions_role_id"),
|
83
|
+
"role_permissions",
|
84
|
+
["role_id"],
|
85
|
+
unique=False,
|
83
86
|
)
|
84
87
|
|
85
88
|
op.create_table(
|
86
|
-
"
|
89
|
+
"users",
|
87
90
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
88
91
|
sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
89
92
|
sa.Column("password", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
93
|
+
sa.Column("active", sa.Boolean(), nullable=False),
|
90
94
|
sa.Column("created_at", sa.DateTime(), nullable=False),
|
91
95
|
sa.Column("created_by", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
92
96
|
sa.Column("updated_at", sa.DateTime(), nullable=True),
|
93
97
|
sa.Column("updated_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
94
98
|
sa.PrimaryKeyConstraint("id"),
|
95
99
|
)
|
96
|
-
op.create_index(op.f("
|
97
|
-
op.create_index(op.f("
|
98
|
-
op.create_index(op.f("
|
99
|
-
op.create_index(op.f("
|
100
|
-
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)
|
101
106
|
|
102
107
|
op.create_table(
|
103
|
-
"
|
108
|
+
"user_roles",
|
104
109
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
105
110
|
sa.Column("user_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
106
111
|
sa.Column("role_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
@@ -108,53 +113,93 @@ def upgrade() -> None:
|
|
108
113
|
sa.Column("created_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
109
114
|
sa.PrimaryKeyConstraint("id"),
|
110
115
|
)
|
111
|
-
op.create_index(
|
112
|
-
|
116
|
+
op.create_index(
|
117
|
+
op.f("ix_user_roles_role_id"), "user_roles", ["role_id"], unique=False
|
118
|
+
)
|
119
|
+
op.create_index(
|
120
|
+
op.f("ix_user_roles_user_id"), "user_roles", ["user_id"], unique=False
|
121
|
+
)
|
113
122
|
|
114
123
|
op.create_table(
|
115
|
-
"
|
124
|
+
"user_sessions",
|
116
125
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
117
126
|
sa.Column("user_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
118
|
-
sa.Column("
|
127
|
+
sa.Column("access_token", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
128
|
+
sa.Column("refresh_token", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
129
|
+
sa.Column("access_token_expired_at", sa.DateTime(), nullable=False),
|
130
|
+
sa.Column("refresh_token_expired_at", sa.DateTime(), nullable=False),
|
119
131
|
sa.PrimaryKeyConstraint("id"),
|
120
132
|
)
|
121
|
-
op.create_index(
|
122
|
-
|
133
|
+
op.create_index(
|
134
|
+
op.f("ix_user_sessions_user_id"), "user_sessions", ["user_id"], unique=False
|
135
|
+
)
|
136
|
+
op.create_index(
|
137
|
+
op.f("ix_user_sessions_access_token"),
|
138
|
+
"user_sessions",
|
139
|
+
["access_token"],
|
140
|
+
unique=False,
|
141
|
+
)
|
142
|
+
op.create_index(
|
143
|
+
op.f("ix_user_sessions_refresh_token"),
|
144
|
+
"user_sessions",
|
145
|
+
["refresh_token"],
|
146
|
+
unique=False,
|
147
|
+
)
|
148
|
+
op.create_index(
|
149
|
+
op.f("ix_user_sessions_access_token_expired_at"),
|
150
|
+
"user_sessions",
|
151
|
+
["access_token_expired_at"],
|
152
|
+
)
|
153
|
+
op.create_index(
|
154
|
+
op.f("ix_user_sessions_refresh_token_expired_at"),
|
155
|
+
"user_sessions",
|
156
|
+
["refresh_token_expired_at"],
|
157
|
+
)
|
123
158
|
# ### end Alembic commands ###
|
124
159
|
|
125
160
|
|
126
161
|
def downgrade() -> None:
|
127
162
|
# ### commands auto generated by Alembic - please adjust! ###
|
128
|
-
op.drop_index(op.f("
|
129
|
-
op.drop_index(op.f("
|
130
|
-
op.
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
op.
|
135
|
-
|
136
|
-
|
137
|
-
op.
|
138
|
-
|
139
|
-
op.drop_index(op.f("
|
140
|
-
op.drop_index(op.f("
|
141
|
-
op.drop_table("
|
142
|
-
|
143
|
-
op.drop_index(op.f("
|
144
|
-
op.drop_index(op.f("
|
145
|
-
op.
|
146
|
-
|
147
|
-
op.drop_index(op.f("
|
148
|
-
op.drop_index(op.f("
|
149
|
-
op.
|
150
|
-
|
151
|
-
op.drop_index(op.f("
|
152
|
-
op.
|
153
|
-
|
154
|
-
|
155
|
-
op.
|
156
|
-
|
157
|
-
op.drop_index(op.f("
|
158
|
-
op.drop_index(op.f("
|
159
|
-
op.
|
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")
|
166
|
+
op.drop_index(
|
167
|
+
op.f("ix_user_sessions_access_token_expired_at"), table_name="user_sessions"
|
168
|
+
)
|
169
|
+
op.drop_index(
|
170
|
+
op.f("ix_user_sessions_refresh_token_expired_at"), table_name="user_sessions"
|
171
|
+
)
|
172
|
+
op.drop_table("user_sessions")
|
173
|
+
|
174
|
+
op.drop_index(op.f("ix_user_roles_user_id"), table_name="user_roles")
|
175
|
+
op.drop_index(op.f("ix_user_roles_role_id"), table_name="user_roles")
|
176
|
+
op.drop_table("user_roles")
|
177
|
+
|
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")
|
184
|
+
op.drop_table("users")
|
185
|
+
|
186
|
+
op.drop_index(op.f("ix_role_permissions_role_id"), table_name="role_permissions")
|
187
|
+
op.drop_index(
|
188
|
+
op.f("ix_role_permissions_permission_id"), table_name="role_permissions"
|
189
|
+
)
|
190
|
+
op.drop_table("role_permissions")
|
191
|
+
|
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")
|
197
|
+
op.drop_table("roles")
|
198
|
+
|
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")
|
204
|
+
op.drop_table("permissions")
|
160
205
|
# ### end Alembic commands ###
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"""create_permissions
|
2
|
+
|
3
|
+
Revision ID: 8ed025bcc845
|
4
|
+
Revises: 3093c7336477
|
5
|
+
Create Date: 2025-02-08 19:09:14.536559
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Sequence, Union
|
10
|
+
|
11
|
+
import sqlalchemy as sa
|
12
|
+
import sqlmodel # 🔥 FastApp Modification
|
13
|
+
from alembic import op
|
14
|
+
from module.auth.migration_metadata import metadata
|
15
|
+
|
16
|
+
# revision identifiers, used by Alembic.
|
17
|
+
revision: str = "8ed025bcc845"
|
18
|
+
down_revision: Union[str, None] = "3093c7336477"
|
19
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
20
|
+
depends_on: Union[str, Sequence[str], None] = None
|
21
|
+
|
22
|
+
|
23
|
+
def upgrade() -> None:
|
24
|
+
op.bulk_insert(
|
25
|
+
metadata.tables["permissions"],
|
26
|
+
[
|
27
|
+
# permission
|
28
|
+
{"name": "permission:create", "description": "create permission"},
|
29
|
+
{"name": "permission:read", "description": "read permission"},
|
30
|
+
{"name": "permission:update", "description": "update permission"},
|
31
|
+
{"name": "permission:delete", "description": "delete permission"},
|
32
|
+
# role
|
33
|
+
{"name": "role:create", "description": "create role"},
|
34
|
+
{"name": "role:read", "description": "read role"},
|
35
|
+
{"name": "role:update", "description": "update role"},
|
36
|
+
{"name": "role:delete", "description": "delete role"},
|
37
|
+
# user
|
38
|
+
{"name": "user:create", "description": "create user"},
|
39
|
+
{"name": "user:read", "description": "read user"},
|
40
|
+
{"name": "user:update", "description": "update user"},
|
41
|
+
{"name": "user:delete", "description": "delete user"},
|
42
|
+
],
|
43
|
+
)
|
44
|
+
# ### end Alembic commands ###
|
45
|
+
|
46
|
+
|
47
|
+
def downgrade() -> None:
|
48
|
+
op.execute(
|
49
|
+
sa.delete(metadata.tables["permissions"]).where(
|
50
|
+
metadata.tables["permissions"].c.name.in_(
|
51
|
+
# user
|
52
|
+
"user:create",
|
53
|
+
"user:read",
|
54
|
+
"user:update",
|
55
|
+
"user:delete",
|
56
|
+
# role
|
57
|
+
"role:create",
|
58
|
+
"role:read",
|
59
|
+
"role:update",
|
60
|
+
"role:delete",
|
61
|
+
# permission
|
62
|
+
"permission:create",
|
63
|
+
"permission:read",
|
64
|
+
"permission:update",
|
65
|
+
"permission:delete",
|
66
|
+
)
|
67
|
+
)
|
68
|
+
)
|
69
|
+
# ### end Alembic commands ###
|