zrb 1.0.0a21__py3-none-any.whl → 1.0.0b5__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/__init__.py +2 -1
- zrb/__main__.py +3 -3
- zrb/builtin/__init__.py +3 -0
- zrb/builtin/group.py +1 -0
- zrb/builtin/llm/llm_chat.py +87 -7
- zrb/builtin/llm/previous-session.js +21 -0
- zrb/builtin/llm/tool/api.py +29 -0
- zrb/builtin/llm/tool/cli.py +1 -1
- zrb/builtin/llm/tool/rag.py +108 -145
- zrb/builtin/llm/tool/web.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +80 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +150 -42
- 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 +113 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service_factory.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_db_repository.py +0 -10
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_repository.py +37 -16
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/{factory.py → my_entity_repository_factory.py} +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +16 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +57 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +74 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +23 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_api_client.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/{any_client.py → my_module_client.py} +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_client_factory.py +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_direct_client.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +11 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +8 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +47 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app_factory.py +29 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +230 -102
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +236 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{db_engine.py → db_engine_factory.py} +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +12 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/logger_factory.py +10 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/parser_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/app.py +47 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +105 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/user_agent.py +58 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +37 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +37 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +169 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client_factory.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_direct_client.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +160 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +18 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +7 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +117 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service_factory.py +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_db_repository.py +26 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository.py +61 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository_factory.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +89 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +67 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository_factory.py +13 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +137 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +179 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +67 -17
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +127 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +43 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +200 -30
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +17 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +78 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +48 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +69 -5
- zrb/builtin/python.py +1 -1
- zrb/builtin/random.py +61 -0
- zrb/builtin/todo.py +1 -0
- zrb/cmd/cmd_val.py +6 -5
- zrb/config.py +15 -4
- zrb/content_transformer/any_content_transformer.py +7 -0
- zrb/content_transformer/content_transformer.py +6 -0
- zrb/input/any_input.py +5 -0
- zrb/input/base_input.py +6 -0
- zrb/input/bool_input.py +2 -0
- zrb/input/float_input.py +2 -0
- zrb/input/int_input.py +2 -0
- zrb/input/option_input.py +2 -0
- zrb/input/password_input.py +2 -0
- zrb/input/text_input.py +2 -0
- zrb/runner/cli.py +14 -7
- zrb/runner/common_util.py +1 -1
- zrb/runner/web_app.py +28 -280
- zrb/runner/web_config/config.py +91 -0
- zrb/runner/web_config/config_factory.py +26 -0
- zrb/runner/web_route/docs_route.py +17 -0
- zrb/runner/web_route/error_page/serve_default_404.py +28 -0
- zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +4 -3
- zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
- zrb/runner/web_route/home_page/home_page_route.py +51 -0
- zrb/runner/web_route/login_api_route.py +31 -0
- zrb/runner/web_route/login_page/login_page_route.py +39 -0
- zrb/runner/web_route/logout_api_route.py +18 -0
- zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
- zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
- zrb/runner/web_route/node_page/node_page_route.py +50 -0
- zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
- zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +1 -1
- zrb/runner/web_route/refresh_token_api_route.py +38 -0
- zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +12 -6
- zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +17 -2
- zrb/runner/web_route/static/static_route.py +44 -0
- zrb/runner/web_route/task_input_api_route.py +47 -0
- zrb/runner/web_route/task_session_api_route.py +147 -0
- zrb/runner/web_schema/session.py +5 -0
- zrb/runner/web_schema/token.py +11 -0
- zrb/runner/web_schema/user.py +32 -0
- zrb/runner/web_util/cookie.py +29 -0
- zrb/runner/{web_util.py → web_util/html.py} +1 -23
- zrb/runner/web_util/token.py +72 -0
- zrb/runner/web_util/user.py +63 -0
- zrb/session/session.py +6 -4
- zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
- zrb/task/base_task.py +56 -8
- zrb/task/base_trigger.py +2 -0
- zrb/task/cmd_task.py +9 -5
- zrb/task/http_check.py +2 -0
- zrb/task/llm_task.py +93 -110
- zrb/task/make_task.py +2 -0
- zrb/task/rsync_task.py +2 -0
- zrb/task/scaffolder.py +8 -5
- zrb/task/scheduler.py +2 -0
- zrb/task/tcp_check.py +2 -0
- zrb/task_status/task_status.py +4 -3
- zrb/util/cmd/command.py +1 -0
- zrb/util/file.py +7 -1
- zrb/util/llm/tool.py +36 -12
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b5.dist-info}/METADATA +3 -2
- zrb-1.0.0b5.dist-info/RECORD +309 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +0 -27
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_usecase.py +0 -65
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +0 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_usecase.py +0 -245
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/any_client.py +0 -33
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +0 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +0 -53
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
- zrb/runner/web_config.py +0 -274
- zrb/runner/web_controller/home_page/controller.py +0 -33
- zrb/runner/web_controller/login_page/controller.py +0 -25
- zrb/runner/web_controller/logout_page/controller.py +0 -26
- zrb-1.0.0a21.dist-info/RECORD +0 -244
- /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
- /zrb/{runner/web_controller → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission}/__init__.py +0 -0
- /zrb/{runner/web_controller/group_info_page → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role}/__init__.py +0 -0
- /zrb/runner/{web_controller/home_page → web_route}/__init__.py +0 -0
- /zrb/runner/{web_controller/session_page → web_route/home_page}/__init__.py +0 -0
- /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
- /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
- /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
- /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
- /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
- /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b5.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b5.dist-info}/entry_points.txt +0 -0
@@ -1,12 +1,22 @@
|
|
1
|
+
import datetime
|
2
|
+
from contextlib import asynccontextmanager
|
1
3
|
from typing import Any, Callable, Generic, Type, TypeVar
|
2
4
|
|
3
|
-
|
4
|
-
from
|
5
|
+
import ulid
|
6
|
+
from my_app_name.common.error import InvalidValueError, NotFoundError
|
7
|
+
from my_app_name.common.parser_factory import (
|
8
|
+
parse_filter_param as default_parse_filter_param,
|
9
|
+
)
|
10
|
+
from my_app_name.common.parser_factory import (
|
11
|
+
parse_sort_param as default_parse_sort_param,
|
12
|
+
)
|
13
|
+
from sqlalchemy import Engine, delete, func, insert, select, update
|
5
14
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
6
|
-
from
|
15
|
+
from sqlalchemy.sql import ClauseElement, ColumnElement, Select
|
16
|
+
from sqlmodel import Session, SQLModel
|
7
17
|
|
8
18
|
DBModel = TypeVar("DBModel", bound=SQLModel)
|
9
|
-
ResponseModel = TypeVar("
|
19
|
+
ResponseModel = TypeVar("ResponseModel", bound=SQLModel)
|
10
20
|
CreateModel = TypeVar("CreateModel", bound=SQLModel)
|
11
21
|
UpdateModel = TypeVar("UpdateModel", bound=SQLModel)
|
12
22
|
|
@@ -19,116 +29,234 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
|
|
19
29
|
entity_name: str = "entity"
|
20
30
|
column_preprocessors: dict[str, Callable[[Any], Any]] = {}
|
21
31
|
|
22
|
-
def __init__(
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
engine: Engine | AsyncEngine,
|
35
|
+
filter_param_parser: (
|
36
|
+
Callable[[SQLModel, str], list[ClauseElement]] | None
|
37
|
+
) = None,
|
38
|
+
sort_param_parser: Callable[[SQLModel, str], list[ColumnElement]] | None = None,
|
39
|
+
):
|
23
40
|
self.engine = engine
|
24
|
-
self.
|
41
|
+
self._is_async = isinstance(engine, AsyncEngine)
|
42
|
+
self._parse_filter_param = (
|
43
|
+
filter_param_parser if filter_param_parser else default_parse_filter_param
|
44
|
+
)
|
45
|
+
self._parse_sort_param = (
|
46
|
+
sort_param_parser if sort_param_parser else default_parse_sort_param
|
47
|
+
)
|
25
48
|
|
26
|
-
|
27
|
-
|
49
|
+
@property
|
50
|
+
def is_async(self) -> bool:
|
51
|
+
return self._is_async
|
28
52
|
|
29
|
-
|
53
|
+
def _select(self) -> Select:
|
54
|
+
"""
|
55
|
+
This method is used to contruct select statement for get, get_by_id and get_by_ids.
|
56
|
+
To parse the result of the statement, make sure you override _rows_to_response as well.
|
57
|
+
"""
|
58
|
+
return select(self.db_model)
|
59
|
+
|
60
|
+
def _rows_to_responses(self, rows: list[tuple[Any, ...]]) -> list[ResponseModel]:
|
61
|
+
"""
|
62
|
+
This method is used to parse the result of select statement generated by _select.
|
63
|
+
"""
|
64
|
+
return [self.response_model.model_validate(row[0]) for row in rows]
|
65
|
+
|
66
|
+
async def _select_to_response(
|
67
|
+
self, query_modifier: Callable[[Select], Any]
|
68
|
+
) -> list[ResponseModel]:
|
69
|
+
statement = query_modifier(self._select())
|
70
|
+
async with self._session_scope() as session:
|
71
|
+
result = await self._execute_statement(session, statement)
|
72
|
+
return self._rows_to_responses(result.all())
|
73
|
+
|
74
|
+
def _ensure_one(self, responses: list[ResponseModel]) -> ResponseModel:
|
75
|
+
if not responses:
|
76
|
+
raise NotFoundError(f"{self.entity_name} not found")
|
77
|
+
if len(responses) > 1:
|
78
|
+
raise InvalidValueError(f"Duplicate {self.entity_name}")
|
79
|
+
return responses[0]
|
80
|
+
|
81
|
+
def _model_to_data_dict(
|
82
|
+
self, data: SQLModel, **additional_data: Any
|
83
|
+
) -> dict[str, Any]:
|
84
|
+
"""
|
85
|
+
This method transform SQLModel into dictionary for insert/update operation.
|
86
|
+
"""
|
30
87
|
data_dict = data.model_dump(exclude_unset=True)
|
88
|
+
data_dict.update(additional_data)
|
31
89
|
for key, preprocessor in self.column_preprocessors.items():
|
32
|
-
if key in data_dict:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
await session.commit()
|
39
|
-
await session.refresh(db_instance)
|
40
|
-
else:
|
41
|
-
with Session(self.engine) as session:
|
42
|
-
session.add(db_instance)
|
43
|
-
session.commit()
|
44
|
-
session.refresh(db_instance)
|
45
|
-
return self._to_response(db_instance)
|
90
|
+
if key not in data_dict:
|
91
|
+
continue
|
92
|
+
if not hasattr(self.db_model, key):
|
93
|
+
raise InvalidValueError(f"Invalid {self.entity_name} property: {key}")
|
94
|
+
data_dict[key] = preprocessor(data_dict[key])
|
95
|
+
return data_dict
|
46
96
|
|
47
|
-
|
97
|
+
@asynccontextmanager
|
98
|
+
async def _session_scope(self):
|
48
99
|
if self.is_async:
|
49
100
|
async with AsyncSession(self.engine) as session:
|
50
|
-
|
101
|
+
async with session.begin():
|
102
|
+
yield session
|
51
103
|
else:
|
52
104
|
with Session(self.engine) as session:
|
53
|
-
|
54
|
-
|
55
|
-
raise NotFoundError(f"{self.entity_name} not found")
|
56
|
-
return self._to_response(db_instance)
|
105
|
+
with session.begin():
|
106
|
+
yield session
|
57
107
|
|
58
|
-
async def
|
59
|
-
offset = (page - 1) * page_size
|
60
|
-
statement = select(self.db_model).offset(offset).limit(page_size)
|
108
|
+
async def _commit(self, session: Session | AsyncSession):
|
61
109
|
if self.is_async:
|
62
|
-
|
63
|
-
result = await session.execute(statement)
|
64
|
-
results = result.scalars().all()
|
110
|
+
await session.commit()
|
65
111
|
else:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
async def update(self, item_id: str, data: UpdateModel) -> ResponseModel:
|
71
|
-
update_data = data.model_dump(exclude_unset=True)
|
72
|
-
for key, value in update_data.items():
|
73
|
-
if key in self.column_preprocessors:
|
74
|
-
update_data[key] = self.column_preprocessors[key](value)
|
75
|
-
if self.is_async:
|
76
|
-
async with AsyncSession(self.engine) as session:
|
77
|
-
db_instance = await session.get(self.db_model, item_id)
|
78
|
-
if not db_instance:
|
79
|
-
raise NotFoundError(f"{self.entity_name} not found")
|
80
|
-
for key, value in update_data.items():
|
81
|
-
setattr(db_instance, key, value)
|
82
|
-
session.add(db_instance)
|
83
|
-
await session.commit()
|
84
|
-
await session.refresh(db_instance)
|
85
|
-
else:
|
86
|
-
with Session(self.engine) as session:
|
87
|
-
db_instance = session.get(self.db_model, item_id)
|
88
|
-
if not db_instance:
|
89
|
-
raise NotFoundError(f"{self.entity_name} not found")
|
90
|
-
for key, value in update_data.items():
|
91
|
-
setattr(db_instance, key, value)
|
92
|
-
session.add(db_instance)
|
93
|
-
session.commit()
|
94
|
-
session.refresh(db_instance)
|
95
|
-
return self._to_response(db_instance)
|
96
|
-
|
97
|
-
async def delete(self, item_id: str) -> ResponseModel:
|
98
|
-
if self.is_async:
|
99
|
-
async with AsyncSession(self.engine) as session:
|
100
|
-
db_instance = await session.get(self.db_model, item_id)
|
101
|
-
if not db_instance:
|
102
|
-
raise NotFoundError(f"{self.entity_name} not found")
|
103
|
-
await session.delete(db_instance)
|
104
|
-
await session.commit()
|
105
|
-
else:
|
106
|
-
with Session(self.engine) as session:
|
107
|
-
db_instance = session.get(self.db_model, item_id)
|
108
|
-
if not db_instance:
|
109
|
-
raise NotFoundError(f"{self.entity_name} not found")
|
110
|
-
session.delete(db_instance)
|
111
|
-
session.commit()
|
112
|
-
return self._to_response(db_instance)
|
113
|
-
|
114
|
-
async def create_bulk(self, data_list: list[CreateModel]) -> list[ResponseModel]:
|
115
|
-
db_instances = []
|
116
|
-
for data in data_list:
|
117
|
-
data_dict = data.model_dump(exclude_unset=True)
|
118
|
-
for key, preprocessor in self.column_preprocessors.items():
|
119
|
-
if key in data_dict:
|
120
|
-
data_dict[key] = preprocessor(data_dict[key])
|
121
|
-
db_instances.append(self.db_model(**data_dict))
|
112
|
+
session.commit()
|
113
|
+
|
114
|
+
async def _execute_statement(self, session, statement: Any) -> Any:
|
122
115
|
if self.is_async:
|
123
|
-
|
124
|
-
session.add_all(db_instances)
|
125
|
-
await session.commit()
|
126
|
-
for instance in db_instances:
|
127
|
-
await session.refresh(instance)
|
116
|
+
return await session.execute(statement)
|
128
117
|
else:
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
118
|
+
return session.execute(statement)
|
119
|
+
|
120
|
+
async def get_by_id(self, id: str) -> ResponseModel:
|
121
|
+
rows = await self._select_to_response(lambda q: q.where(self.db_model.id == id))
|
122
|
+
return self._ensure_one(rows)
|
123
|
+
|
124
|
+
async def get_by_ids(self, id_list: list[str]) -> list[ResponseModel]:
|
125
|
+
return await self._select_to_response(
|
126
|
+
lambda q: q.where(self.db_model.id.in_(id_list))
|
127
|
+
)
|
128
|
+
|
129
|
+
async def count(self, filter: str | None = None) -> int:
|
130
|
+
count_statement = select(func.count(1)).select_from(self.db_model)
|
131
|
+
if filter:
|
132
|
+
filter_param = self._parse_filter_param(self.db_model, filter)
|
133
|
+
count_statement = count_statement.where(*filter_param)
|
134
|
+
async with self._session_scope() as session:
|
135
|
+
result = await self._execute_statement(session, count_statement)
|
136
|
+
return result.scalar_one()
|
137
|
+
|
138
|
+
async def get(
|
139
|
+
self,
|
140
|
+
page: int = 1,
|
141
|
+
page_size: int = 10,
|
142
|
+
filter: str | None = None,
|
143
|
+
sort: str | None = None,
|
144
|
+
) -> list[ResponseModel]:
|
145
|
+
return await self._select_to_response(
|
146
|
+
self._get_pagination_query_modifier(
|
147
|
+
page=page, page_size=page_size, filter=filter, sort=sort
|
148
|
+
)
|
149
|
+
)
|
150
|
+
|
151
|
+
def _get_pagination_query_modifier(
|
152
|
+
self,
|
153
|
+
page: int = 1,
|
154
|
+
page_size: int = 10,
|
155
|
+
filter: str | None = None,
|
156
|
+
sort: str | None = None,
|
157
|
+
) -> Callable[[Select], Any]:
|
158
|
+
def pagination_query_modifier(statement: Select) -> Any:
|
159
|
+
offset = (page - 1) * page_size
|
160
|
+
statement = statement.offset(offset).limit(page_size)
|
161
|
+
if filter:
|
162
|
+
filter_param = self._parse_filter_param(self.db_model, filter)
|
163
|
+
statement = statement.where(*filter_param)
|
164
|
+
if sort:
|
165
|
+
sort_param = self._parse_sort_param(self.db_model, sort)
|
166
|
+
statement = statement.order_by(*sort_param)
|
167
|
+
return statement
|
168
|
+
|
169
|
+
return pagination_query_modifier
|
170
|
+
|
171
|
+
async def create(self, data: CreateModel) -> DBModel:
|
172
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
173
|
+
data_dict = self._model_to_data_dict(data, created_at=now, id=ulid.new().str)
|
174
|
+
async with self._session_scope() as session:
|
175
|
+
await self._execute_statement(
|
176
|
+
session, insert(self.db_model).values(**data_dict)
|
177
|
+
)
|
178
|
+
statement = select(self.db_model).where(self.db_model.id == data_dict["id"])
|
179
|
+
result = await self._execute_statement(session, statement)
|
180
|
+
created_entity = result.scalar_one_or_none()
|
181
|
+
if created_entity is None:
|
182
|
+
raise NotFoundError(f"{self.entity_name} not found after creation")
|
183
|
+
return self.db_model(**created_entity.model_dump())
|
184
|
+
|
185
|
+
async def create_bulk(self, data_list: list[CreateModel]) -> list[DBModel]:
|
186
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
187
|
+
data_dicts = [
|
188
|
+
self._model_to_data_dict(data, created_at=now, id=ulid.new().str)
|
189
|
+
for data in data_list
|
190
|
+
]
|
191
|
+
async with self._session_scope() as session:
|
192
|
+
await self._execute_statement(
|
193
|
+
session, insert(self.db_model).values(data_dicts)
|
194
|
+
)
|
195
|
+
id_list = [d["id"] for d in data_dicts]
|
196
|
+
statement = select(self.db_model).where(self.db_model.id.in_(id_list))
|
197
|
+
result = await self._execute_statement(session, statement)
|
198
|
+
return [
|
199
|
+
self.db_model(**entity.model_dump())
|
200
|
+
for entity in result.scalars().all()
|
201
|
+
]
|
202
|
+
|
203
|
+
async def delete(self, id: str) -> DBModel:
|
204
|
+
async with self._session_scope() as session:
|
205
|
+
statement = select(self.db_model).where(self.db_model.id == id)
|
206
|
+
result = await self._execute_statement(session, statement)
|
207
|
+
entity = result.scalar_one_or_none()
|
208
|
+
if not entity:
|
209
|
+
raise NotFoundError(f"{self.entity_name} not found")
|
210
|
+
await self._execute_statement(
|
211
|
+
session, delete(self.db_model).where(self.db_model.id == id)
|
212
|
+
)
|
213
|
+
return self.db_model(**entity.model_dump())
|
214
|
+
|
215
|
+
async def delete_bulk(self, id_list: list[str]) -> list[DBModel]:
|
216
|
+
async with self._session_scope() as session:
|
217
|
+
statement = select(self.db_model).where(self.db_model.id.in_(id_list))
|
218
|
+
result = await self._execute_statement(session, statement)
|
219
|
+
entities = result.scalars().all()
|
220
|
+
await self._execute_statement(
|
221
|
+
session, delete(self.db_model).where(self.db_model.id.in_(id_list))
|
222
|
+
)
|
223
|
+
return [self.db_model(**entity.model_dump()) for entity in entities]
|
224
|
+
|
225
|
+
async def update(self, id: str, data: UpdateModel) -> DBModel:
|
226
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
227
|
+
update_data = self._model_to_data_dict(data, updated_at=now)
|
228
|
+
async with self._session_scope() as session:
|
229
|
+
statement = (
|
230
|
+
update(self.db_model)
|
231
|
+
.where(self.db_model.id == id)
|
232
|
+
.values(**update_data)
|
233
|
+
)
|
234
|
+
await self._execute_statement(session, statement)
|
235
|
+
result = await self._execute_statement(
|
236
|
+
session, select(self.db_model).where(self.db_model.id == id)
|
237
|
+
)
|
238
|
+
updated_instance = result.scalar_one_or_none()
|
239
|
+
if not updated_instance:
|
240
|
+
raise NotFoundError(f"{self.entity_name} not found")
|
241
|
+
return self.db_model(**updated_instance.model_dump())
|
242
|
+
|
243
|
+
async def update_bulk(self, id_list: list[str], data: UpdateModel) -> list[DBModel]:
|
244
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
245
|
+
update_data = self._model_to_data_dict(data, updated_at=now)
|
246
|
+
update_data = {k: v for k, v in update_data.items() if v is not None}
|
247
|
+
if not update_data:
|
248
|
+
raise InvalidValueError("No valid update data provided")
|
249
|
+
async with self._session_scope() as session:
|
250
|
+
statement = (
|
251
|
+
update(self.db_model)
|
252
|
+
.where(self.db_model.id.in_(id_list))
|
253
|
+
.values(**update_data)
|
254
|
+
)
|
255
|
+
await self._execute_statement(session, statement)
|
256
|
+
result = await self._execute_statement(
|
257
|
+
session, select(self.db_model).where(self.db_model.id.in_(id_list))
|
258
|
+
)
|
259
|
+
return [
|
260
|
+
self.db_model(**entity.model_dump())
|
261
|
+
for entity in result.scalars().all()
|
262
|
+
]
|
@@ -0,0 +1,236 @@
|
|
1
|
+
import inspect
|
2
|
+
from enum import Enum
|
3
|
+
from functools import partial
|
4
|
+
from logging import Logger
|
5
|
+
from typing import Any, Callable, Sequence
|
6
|
+
|
7
|
+
import httpx
|
8
|
+
from fastapi import APIRouter, Depends, params
|
9
|
+
from my_app_name.common.error import ClientAPIError
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
|
13
|
+
class RouteParam:
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
path: str,
|
17
|
+
response_model: Any,
|
18
|
+
status_code: int | None = None,
|
19
|
+
tags: list[str | Enum] | None = None,
|
20
|
+
summary: str | None = None,
|
21
|
+
description: str = "",
|
22
|
+
deprecated: bool | None = None,
|
23
|
+
methods: set[str] | list[str] | None = None,
|
24
|
+
func: Callable | None = None,
|
25
|
+
):
|
26
|
+
self.path = path
|
27
|
+
self.response_model = response_model
|
28
|
+
self.status_code = status_code
|
29
|
+
self.tags = tags
|
30
|
+
self.summary = summary
|
31
|
+
self.description = description
|
32
|
+
self.deprecated = deprecated
|
33
|
+
self.methods = methods
|
34
|
+
self.func = func
|
35
|
+
|
36
|
+
|
37
|
+
class BaseService:
|
38
|
+
_route_params: dict[str, RouteParam] = {}
|
39
|
+
|
40
|
+
def __init__(self, logger: Logger):
|
41
|
+
self._logger = logger
|
42
|
+
self._route_params: dict[str, RouteParam] = {}
|
43
|
+
for name, method in self.__class__.__dict__.items():
|
44
|
+
if hasattr(method, "__route_param__"):
|
45
|
+
self._route_params[name] = getattr(method, "__route_param__")
|
46
|
+
|
47
|
+
@property
|
48
|
+
def logger(self) -> Logger:
|
49
|
+
return self._logger
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def route(
|
53
|
+
cls,
|
54
|
+
path: str,
|
55
|
+
*,
|
56
|
+
response_model: Any = None,
|
57
|
+
status_code: int | None = None,
|
58
|
+
tags: list[str | Enum] | None = None,
|
59
|
+
dependencies: Sequence[params.Depends] | None = None,
|
60
|
+
summary: str | None = None,
|
61
|
+
description: str = None,
|
62
|
+
deprecated: bool | None = None,
|
63
|
+
methods: set[str] | list[str] | None = None,
|
64
|
+
):
|
65
|
+
"""
|
66
|
+
Decorator to register a method with its HTTP details.
|
67
|
+
"""
|
68
|
+
|
69
|
+
def decorator(func: Callable):
|
70
|
+
# Check for Depends in function parameters
|
71
|
+
sig = inspect.signature(func)
|
72
|
+
for param in sig.parameters.values():
|
73
|
+
if param.annotation is Depends or (
|
74
|
+
hasattr(param.annotation, "__origin__")
|
75
|
+
and param.annotation.__origin__ is Depends
|
76
|
+
):
|
77
|
+
raise ValueError(
|
78
|
+
f"Depends is not allowed in function parameters. Found in {func.__name__}" # noqa
|
79
|
+
)
|
80
|
+
# Inject __route_param__ property to the method
|
81
|
+
# Method with __route_param__ property will automatically
|
82
|
+
# registered to self._route_param and will be automatically exposed
|
83
|
+
# into DirectClient and APIClient
|
84
|
+
func.__route_param__ = RouteParam(
|
85
|
+
path=path,
|
86
|
+
response_model=response_model,
|
87
|
+
status_code=status_code,
|
88
|
+
tags=tags,
|
89
|
+
summary=summary,
|
90
|
+
description=description,
|
91
|
+
deprecated=deprecated,
|
92
|
+
methods=methods,
|
93
|
+
func=func,
|
94
|
+
)
|
95
|
+
return func
|
96
|
+
|
97
|
+
return decorator
|
98
|
+
|
99
|
+
def as_direct_client(self):
|
100
|
+
"""
|
101
|
+
Dynamically create a direct client class.
|
102
|
+
"""
|
103
|
+
_methods = self._route_params
|
104
|
+
DirectClient = _create_client_class("DirectClient")
|
105
|
+
for name, details in _methods.items():
|
106
|
+
func = details.func
|
107
|
+
client_method = _create_direct_client_method(self._logger, func, self)
|
108
|
+
# Use __get__ to make a bounded method,
|
109
|
+
# ensuring that client_method use DirectClient as `self`
|
110
|
+
setattr(DirectClient, name, client_method.__get__(DirectClient))
|
111
|
+
return DirectClient
|
112
|
+
|
113
|
+
def as_api_client(self, base_url: str):
|
114
|
+
"""
|
115
|
+
Dynamically create an API client class.
|
116
|
+
"""
|
117
|
+
_methods = self._route_params
|
118
|
+
APIClient = _create_client_class("APIClient")
|
119
|
+
# Dynamically generate methods
|
120
|
+
for name, param in _methods.items():
|
121
|
+
client_method = _create_api_client_method(self._logger, param, base_url)
|
122
|
+
# Use __get__ to make a bounded method,
|
123
|
+
# ensuring that client_method use APIClient as `self`
|
124
|
+
setattr(APIClient, name, client_method.__get__(APIClient))
|
125
|
+
return APIClient
|
126
|
+
|
127
|
+
def serve_route(self, app: APIRouter):
|
128
|
+
"""
|
129
|
+
Dynamically add routes to FastAPI.
|
130
|
+
"""
|
131
|
+
for _, route_param in self._route_params.items():
|
132
|
+
bound_func = partial(route_param.func, self)
|
133
|
+
bound_func.__name__ = route_param.func.__name__
|
134
|
+
bound_func.__doc__ = route_param.func.__doc__
|
135
|
+
app.add_api_route(
|
136
|
+
path=route_param.path,
|
137
|
+
endpoint=bound_func,
|
138
|
+
response_model=route_param.response_model,
|
139
|
+
status_code=route_param.status_code,
|
140
|
+
tags=route_param.tags,
|
141
|
+
summary=route_param.summary,
|
142
|
+
description=route_param.description,
|
143
|
+
deprecated=route_param.deprecated,
|
144
|
+
methods=route_param.methods,
|
145
|
+
)
|
146
|
+
|
147
|
+
|
148
|
+
def _create_client_class(name):
|
149
|
+
class Client:
|
150
|
+
pass
|
151
|
+
|
152
|
+
Client.__name__ = name
|
153
|
+
return Client
|
154
|
+
|
155
|
+
|
156
|
+
def _create_direct_client_method(logger: Logger, func: Callable, service: BaseService):
|
157
|
+
async def client_method(self, *args, **kwargs):
|
158
|
+
return await func(service, *args, **kwargs)
|
159
|
+
|
160
|
+
return client_method
|
161
|
+
|
162
|
+
|
163
|
+
def _create_api_client_method(logger: Logger, param: RouteParam, base_url: str):
|
164
|
+
async def client_method(*args, **kwargs):
|
165
|
+
url = base_url + param.path
|
166
|
+
method = (
|
167
|
+
param.methods[0].lower()
|
168
|
+
if isinstance(param.methods, list)
|
169
|
+
else param.methods.lower()
|
170
|
+
)
|
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
|
+
# Format the URL with path parameters
|
202
|
+
url = url.format(**path_params)
|
203
|
+
logger.info(
|
204
|
+
f"Sending request to {url} with method {method}, json={body}, params={query_params}" # noqa
|
205
|
+
)
|
206
|
+
async with httpx.AsyncClient() as client:
|
207
|
+
if method in ["get", "delete"]:
|
208
|
+
response = await getattr(client, method)(url, params=query_params)
|
209
|
+
else:
|
210
|
+
response = await getattr(client, method)(
|
211
|
+
url, json=body, params=query_params
|
212
|
+
)
|
213
|
+
logger.info(
|
214
|
+
f"Received response: status={response.status_code}, content={response.content}"
|
215
|
+
)
|
216
|
+
if response.status_code >= 400:
|
217
|
+
error_detail = (
|
218
|
+
response.json()
|
219
|
+
if response.headers.get("content-type") == "application/json"
|
220
|
+
else response.text
|
221
|
+
)
|
222
|
+
raise ClientAPIError(response.status_code, error_detail)
|
223
|
+
return response.json()
|
224
|
+
|
225
|
+
return client_method
|
226
|
+
|
227
|
+
|
228
|
+
def _parse_api_param(data: Any) -> Any:
|
229
|
+
if isinstance(data, BaseModel):
|
230
|
+
return data.model_dump()
|
231
|
+
elif isinstance(data, list):
|
232
|
+
return [_parse_api_param(item) for item in data]
|
233
|
+
elif isinstance(data, dict):
|
234
|
+
return {key: _parse_api_param(value) for key, value in data.items()}
|
235
|
+
else:
|
236
|
+
return data
|
@@ -6,3 +6,15 @@ from fastapi import HTTPException
|
|
6
6
|
class NotFoundError(HTTPException):
|
7
7
|
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
8
8
|
super().__init__(404, {"message": message}, headers)
|
9
|
+
|
10
|
+
|
11
|
+
class InvalidValueError(HTTPException):
|
12
|
+
def __init__(self, message: str, headers: Dict[str, str] | None = None) -> None:
|
13
|
+
super().__init__(422, {"message": message}, headers)
|
14
|
+
|
15
|
+
|
16
|
+
class ClientAPIError(HTTPException):
|
17
|
+
def __init__(
|
18
|
+
self, status_code: int, message: str, headers: Dict[str, str] | None = None
|
19
|
+
) -> None:
|
20
|
+
super().__init__(status_code, {"message": message}, headers)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import os
|
2
|
+
from contextlib import asynccontextmanager
|
3
|
+
|
4
|
+
from fastapi import FastAPI, HTTPException
|
5
|
+
from fastapi.openapi.docs import get_swagger_ui_html
|
6
|
+
from fastapi.responses import FileResponse
|
7
|
+
from fastapi.staticfiles import StaticFiles
|
8
|
+
from sqlalchemy.engine import Engine
|
9
|
+
from sqlmodel import SQLModel
|
10
|
+
from starlette.types import Lifespan
|
11
|
+
|
12
|
+
|
13
|
+
def get_default_app_title(app_title: str, mode: str, modules: list[str] = []) -> str:
|
14
|
+
if mode == "monolith":
|
15
|
+
return app_title
|
16
|
+
return f"{app_title} - {', '.join(modules)}"
|
17
|
+
|
18
|
+
|
19
|
+
def create_default_app_lifespan(db_engine: Engine) -> Lifespan:
|
20
|
+
@asynccontextmanager
|
21
|
+
async def default_app_lifespan(app: FastAPI):
|
22
|
+
SQLModel.metadata.create_all(db_engine)
|
23
|
+
yield
|
24
|
+
|
25
|
+
return default_app_lifespan
|
26
|
+
|
27
|
+
|
28
|
+
def serve_static_dir(app: FastAPI, static_dir: str):
|
29
|
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
30
|
+
|
31
|
+
# Serve static files
|
32
|
+
@app.get("/static/{file_path:path}", include_in_schema=False)
|
33
|
+
async def static_files(file_path: str):
|
34
|
+
full_path = os.path.join(static_dir, file_path)
|
35
|
+
if os.path.isfile(full_path):
|
36
|
+
return FileResponse(full_path)
|
37
|
+
raise HTTPException(status_code=404, detail="File not found")
|
38
|
+
|
39
|
+
|
40
|
+
def serve_docs(app: FastAPI, app_title: str, favicon_url: str):
|
41
|
+
@app.get("/docs", include_in_schema=False)
|
42
|
+
async def swagger_ui_html():
|
43
|
+
return get_swagger_ui_html(
|
44
|
+
openapi_url="/openapi.json",
|
45
|
+
title=app_title,
|
46
|
+
swagger_favicon_url=favicon_url,
|
47
|
+
)
|