zrb 1.0.0a21__py3-none-any.whl → 1.0.0b2__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 +0 -3
- zrb/builtin/__init__.py +3 -0
- zrb/builtin/group.py +1 -0
- zrb/builtin/llm/llm_chat.py +2 -2
- 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 +72 -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 +185 -101
- 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 +25 -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 +163 -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 +75 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +59 -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 +105 -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 +42 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +38 -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 +105 -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 +198 -28
- 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 +50 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +52 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +30 -5
- zrb/builtin/python.py +1 -1
- zrb/builtin/random.py +61 -0
- zrb/cmd/cmd_val.py +6 -5
- zrb/content_transformer/any_content_transformer.py +7 -0
- zrb/content_transformer/content_transformer.py +6 -0
- zrb/runner/cli.py +14 -7
- 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} +2 -2
- 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_route/refresh_token_api_route.py +38 -0
- 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 +102 -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 +53 -6
- 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 +2 -0
- 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-1.0.0a21.dist-info → zrb-1.0.0b2.dist-info}/METADATA +1 -1
- zrb-1.0.0b2.dist-info/RECORD +307 -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/{web_controller/session_page → web_route/node_page/task}/view.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/current-session.js +0 -0
- /zrb/runner/{web_controller/static → web_route/static/resources}/session/event.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.0b2.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a21.dist-info → zrb-1.0.0b2.dist-info}/entry_points.txt +0 -0
@@ -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
|
+
)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Callable
|
3
|
+
|
4
|
+
from sqlalchemy import asc, desc
|
5
|
+
from sqlalchemy.sql import ClauseElement, ColumnElement
|
6
|
+
from sqlmodel import SQLModel
|
7
|
+
|
8
|
+
|
9
|
+
def create_default_sort_param_parser() -> (
|
10
|
+
Callable[[SQLModel, str], list[ColumnElement]]
|
11
|
+
):
|
12
|
+
def parse_sort_param(model: SQLModel, query: str) -> list[ColumnElement]:
|
13
|
+
"""
|
14
|
+
Parse the sort parameter and return a list of SQLAlchemy ColumnElement objects.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
model (Type[SQLModel]): The SQLModel class to be queried.
|
18
|
+
query (str): A comma-separated string of fields to sort by.
|
19
|
+
Prefix a field with '-' for descending order.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
list[ColumnElement]: A list of SQLAlchemy ColumnElement objects representing the sort order.
|
23
|
+
|
24
|
+
Example:
|
25
|
+
parse_sort_param(UserModel, "name,-age")
|
26
|
+
# Returns [asc(UserModel.name), desc(UserModel.age)]
|
27
|
+
"""
|
28
|
+
sorts: list[ColumnElement] = []
|
29
|
+
sort_parts = query.split(",")
|
30
|
+
for part in sort_parts:
|
31
|
+
if part.startswith("-"):
|
32
|
+
key = part[1:]
|
33
|
+
order = desc
|
34
|
+
else:
|
35
|
+
key = part
|
36
|
+
order = asc
|
37
|
+
if hasattr(model, key):
|
38
|
+
sorts.append(order(getattr(model, key)))
|
39
|
+
return sorts
|
40
|
+
|
41
|
+
return parse_sort_param
|
42
|
+
|
43
|
+
|
44
|
+
def create_default_filter_param_parser() -> (
|
45
|
+
Callable[[SQLModel, str], list[ClauseElement]]
|
46
|
+
):
|
47
|
+
def parse_filter_param(model: SQLModel, query: str) -> list[ClauseElement]:
|
48
|
+
"""
|
49
|
+
Parse the filter parameter and return a list of SQLAlchemy ClauseElement objects.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
model (Type[SQLModel]): The SQLModel class to be queried.
|
53
|
+
query (str): A comma-separated string of filters.
|
54
|
+
Each filter should be in the format "field:operator:value".
|
55
|
+
Use '\,' to escape commas within values.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
list[ClauseElement]: A list of SQLAlchemy ClauseElement objects representing the filters.
|
59
|
+
|
60
|
+
Supported operators:
|
61
|
+
eq: Equal to
|
62
|
+
ne: Not equal to
|
63
|
+
gt: Greater than
|
64
|
+
gte: Greater than or equal to
|
65
|
+
lt: Less than
|
66
|
+
lte: Less than or equal to
|
67
|
+
like: SQL LIKE (use % for wildcards)
|
68
|
+
in: In a list of values (semicolon-separated)
|
69
|
+
|
70
|
+
Example:
|
71
|
+
parse_filter_param(UserModel, "age:gte:18,name:like:John%,role:in:admin;user,address:eq:123\, Main St.")
|
72
|
+
# Returns [UserModel.age >= 18, UserModel.name.like("John%"), UserModel.role.in_(["admin", "user"]), UserModel.address == "123, Main St."]
|
73
|
+
"""
|
74
|
+
filters: list[ClauseElement] = []
|
75
|
+
filter_parts = split_by_comma(query)
|
76
|
+
for part in filter_parts:
|
77
|
+
match = re.match(r"(.+):(.+):(.+)", part)
|
78
|
+
if match:
|
79
|
+
key, op, value = match.groups()
|
80
|
+
value = value.replace(r"\,", ",") # Unescape commas in the value
|
81
|
+
if hasattr(model, key):
|
82
|
+
column = getattr(model, key)
|
83
|
+
if op == "eq":
|
84
|
+
filters.append(column == value)
|
85
|
+
elif op == "ne":
|
86
|
+
filters.append(column != value)
|
87
|
+
elif op == "gt":
|
88
|
+
filters.append(column > value)
|
89
|
+
elif op == "gte":
|
90
|
+
filters.append(column >= value)
|
91
|
+
elif op == "lt":
|
92
|
+
filters.append(column < value)
|
93
|
+
elif op == "lte":
|
94
|
+
filters.append(column <= value)
|
95
|
+
elif op == "like":
|
96
|
+
filters.append(column.like(value))
|
97
|
+
elif op == "in":
|
98
|
+
filters.append(column.in_(value.split(";")))
|
99
|
+
return filters
|
100
|
+
|
101
|
+
return parse_filter_param
|
102
|
+
|
103
|
+
|
104
|
+
def split_by_comma(s: str, delimiter: str = ",") -> list[str]:
|
105
|
+
return re.split(r"(?<!\\)" + re.escape(delimiter), s)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
|
4
|
+
def get_os_from_user_agent(user_agent: str) -> str:
|
5
|
+
os_patterns = [
|
6
|
+
(r"Windows NT (\d+\.\d+)", "Windows"),
|
7
|
+
(r"Mac OS X (\d+[_\.]\d+)", "MacOS"),
|
8
|
+
(r"Android (\d+[\.\d]*)", "Android"),
|
9
|
+
(r"iPhone OS (\d+[_\.]\d+)", "iOS"),
|
10
|
+
(r"iPad.*OS (\d+[_\.]\d+)", "iPadOS"),
|
11
|
+
(r"Linux", "Linux"),
|
12
|
+
]
|
13
|
+
os = "Unknown OS"
|
14
|
+
# Match OS
|
15
|
+
for pattern, name in os_patterns:
|
16
|
+
match = re.search(pattern, user_agent)
|
17
|
+
if match:
|
18
|
+
os = (
|
19
|
+
f"{name} {match.group(1).replace('_', '.')}" if match.groups() else name
|
20
|
+
)
|
21
|
+
break
|
22
|
+
return os
|
23
|
+
|
24
|
+
|
25
|
+
def get_browser_from_user_agent(user_agent: str) -> str:
|
26
|
+
browser_patterns = [
|
27
|
+
(r"Chrome/([\d\.]+)", "Chrome"),
|
28
|
+
(r"Firefox/([\d\.]+)", "Firefox"),
|
29
|
+
(r"Edg/([\d\.]+)", "Edge"),
|
30
|
+
(r"Safari/([\d\.]+)", "Safari"),
|
31
|
+
]
|
32
|
+
browser = "Unknown Browser"
|
33
|
+
# Match Browser
|
34
|
+
for pattern, name in browser_patterns:
|
35
|
+
match = re.search(pattern, user_agent)
|
36
|
+
if match:
|
37
|
+
browser = f"{name} {match.group(1)}"
|
38
|
+
break
|
39
|
+
return browser
|
40
|
+
|
41
|
+
|
42
|
+
def get_device_from_user_agent(user_agent: str):
|
43
|
+
device_patterns = [
|
44
|
+
(r"iPhone", "iPhone"),
|
45
|
+
(r"iPad", "iPad"),
|
46
|
+
(r"Android.*Mobile", "Android Phone"),
|
47
|
+
(r"Android", "Android Tablet"),
|
48
|
+
(r"Macintosh", "Mac"),
|
49
|
+
(r"Windows NT", "Windows PC"),
|
50
|
+
(r"Linux", "Linux Device"),
|
51
|
+
]
|
52
|
+
device = "Unknown Device"
|
53
|
+
# Match Device
|
54
|
+
for pattern, name in device_patterns:
|
55
|
+
if re.search(pattern, user_agent):
|
56
|
+
device = name
|
57
|
+
break
|
58
|
+
return device
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from fastapi.responses import HTMLResponse
|
5
|
+
from jinja2 import Environment, FileSystemLoader
|
6
|
+
|
7
|
+
|
8
|
+
def render_str(template_path: str, **data: Any) -> str:
|
9
|
+
"""
|
10
|
+
Renders a Jinja2 template with the given data.
|
11
|
+
|
12
|
+
:param template_path: The absolute or relative file path of the template.
|
13
|
+
:param data: A dictionary of data to pass to the template (default is None).
|
14
|
+
:return: Rendered template as a string.
|
15
|
+
"""
|
16
|
+
if data is None:
|
17
|
+
data = {}
|
18
|
+
# Get the directory and file name
|
19
|
+
directory, template_name = os.path.split(template_path)
|
20
|
+
# Set up Jinja2 environment with the specific directory
|
21
|
+
env = Environment(loader=FileSystemLoader(directory))
|
22
|
+
# Render the template
|
23
|
+
template = env.get_template(template_name)
|
24
|
+
return template.render(data)
|
25
|
+
|
26
|
+
|
27
|
+
def render_page(
|
28
|
+
template_path: str,
|
29
|
+
status_code: int = 200,
|
30
|
+
headers: dict[str, str] = None,
|
31
|
+
media_type: str | None = None,
|
32
|
+
**data: Any
|
33
|
+
) -> HTMLResponse:
|
34
|
+
content = render_str(template_path, **data)
|
35
|
+
return HTMLResponse(
|
36
|
+
content=content, status_code=status_code, headers=headers, media_type=media_type
|
37
|
+
)
|
@@ -1,6 +1,30 @@
|
|
1
1
|
import os
|
2
2
|
|
3
3
|
APP_PATH = os.path.dirname(__file__)
|
4
|
+
APP_VERSION = "0.1.0"
|
5
|
+
|
6
|
+
APP_GATEWAY_VIEW_PATH = os.path.join(APP_PATH, "module", "gateway", "view")
|
7
|
+
APP_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH = os.getenv(
|
8
|
+
"MY_APP_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH",
|
9
|
+
os.path.join("template", "default.html"),
|
10
|
+
)
|
11
|
+
_DEFAULT_CSS_PATH = "/static/pico-css/pico.min.css"
|
12
|
+
APP_GATEWAY_CSS_PATH_LIST = [
|
13
|
+
path
|
14
|
+
for path in os.getenv("MY_APP_GATEWAY_CSS_PATH", _DEFAULT_CSS_PATH).split(":")
|
15
|
+
if path != ""
|
16
|
+
]
|
17
|
+
APP_GATEWAY_JS_PATH_LIST = [
|
18
|
+
path for path in os.getenv("MY_APP_GATEWAY_JS_PATH", "").split(":") if path != ""
|
19
|
+
]
|
20
|
+
APP_GATEWAY_TITLE = os.getenv("MY_APP_GATEWAY_TITLE", "My App Name")
|
21
|
+
APP_GATEWAY_SUBTITLE = os.getenv("MY_APP_GATEWAY_SUBTITLE", "Just Another App")
|
22
|
+
APP_GATEWAY_LOGO_PATH = os.getenv(
|
23
|
+
"MY_APP_GATEWAY_LOGO", "/static/images/android-chrome-192x192.png"
|
24
|
+
)
|
25
|
+
APP_GATEWAY_FAVICON_PATH = os.getenv(
|
26
|
+
"MY_APP_GATEWAY_FAVICON", "/static/images/favicon-32x32.png"
|
27
|
+
)
|
4
28
|
|
5
29
|
APP_MODE = os.getenv("MY_APP_NAME_MODE", "monolith")
|
6
30
|
APP_MODULES = [
|
@@ -15,7 +39,7 @@ APP_COMMUNICATION = os.getenv(
|
|
15
39
|
)
|
16
40
|
APP_REPOSITORY_TYPE = os.getenv("APP_REPOSITORY_TYPE", "db")
|
17
41
|
APP_DB_URL = os.getenv(
|
18
|
-
"
|
42
|
+
"MY_APP_NAME_DB_URL",
|
19
43
|
(
|
20
44
|
f"sqlite:///{APP_PATH}/monolith.db"
|
21
45
|
if APP_MODE == "monolith" or len(APP_MODULES) == 0
|
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
from my_app_name.config import APP_AUTH_BASE_URL
|
2
|
+
from my_app_name.module.auth.client.auth_client import AuthClient
|
3
|
+
from my_app_name.module.auth.service.permission.permission_service_factory import (
|
4
|
+
permission_service,
|
5
|
+
)
|
6
|
+
from my_app_name.module.auth.service.role.role_service_factory import role_service
|
7
|
+
from my_app_name.module.auth.service.user.user_service_factory import user_service
|
8
|
+
|
9
|
+
|
10
|
+
class AuthAPIClient(
|
11
|
+
permission_service.as_api_client(base_url=APP_AUTH_BASE_URL),
|
12
|
+
role_service.as_api_client(base_url=APP_AUTH_BASE_URL),
|
13
|
+
user_service.as_api_client(base_url=APP_AUTH_BASE_URL),
|
14
|
+
AuthClient,
|
15
|
+
):
|
16
|
+
pass
|