faster-app 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ """
2
+ 模型基类, 使用 pydantic 库管理模型
3
+ """
4
+
5
+ from enum import IntEnum, StrEnum
6
+ from tortoise import Model
7
+ from tortoise.fields import (
8
+ IntEnumField,
9
+ UUIDField,
10
+ DatetimeField,
11
+ CharEnumField,
12
+ BooleanField,
13
+ TextField,
14
+ )
15
+
16
+
17
+ class UUIDModel(Model):
18
+ """模型基类"""
19
+
20
+ id = UUIDField(pk=True, verbose_name="ID")
21
+
22
+ class Meta:
23
+ abstract = True
24
+
25
+
26
+ class DateTimeModel(Model):
27
+ """模型基类"""
28
+
29
+ created_at = DatetimeField(auto_now_add=True, verbose_name="创建时间")
30
+ updated_at = DatetimeField(auto_now=True, verbose_name="更新时间")
31
+
32
+ class Meta:
33
+ abstract = True
34
+
35
+
36
+ class StatusModel(Model):
37
+ """模型基类"""
38
+
39
+ class StatusEnum(IntEnum):
40
+ """状态枚举"""
41
+
42
+ ACTIVE = 1
43
+ INACTIVE = 0
44
+
45
+ status = IntEnumField(default=1, verbose_name="状态", enum_type=StatusEnum)
46
+
47
+ class Meta:
48
+ abstract = True
49
+
50
+ def validate_status(self):
51
+ """如果状态枚举 不存在或不是 IntEnum 类型, 则抛出异常"""
52
+ # 检查是否存在 StatusEnum 属性
53
+ if not hasattr(self, "StatusEnum"):
54
+ raise ValueError(f"{self.__class__.__name__} 必须定义 StatusEnum")
55
+
56
+ # 检查 StatusEnum 是否是 IntEnum 的子类
57
+ if not (
58
+ isinstance(self.StatusEnum, type) and issubclass(self.StatusEnum, IntEnum)
59
+ ):
60
+ raise ValueError(
61
+ f"{self.__class__.__name__}.StatusEnum 必须是 IntEnum 的子类"
62
+ )
63
+
64
+ # 检查当前状态值是否在枚举中
65
+ try:
66
+ self.StatusEnum(self.status)
67
+ except ValueError:
68
+ valid_values = [e.value for e in self.StatusEnum]
69
+ raise ValueError(f"状态值 {self.status} 不在有效枚举值 {valid_values} 中")
70
+
71
+
72
+ class ScopeModel(Model):
73
+ """作用域模型基类,存储作用域"""
74
+
75
+ class ScopeEnum(StrEnum):
76
+ """作用域枚举"""
77
+
78
+ SYSTEM = "system"
79
+ TENANT = "tenant"
80
+ PROJECT = "project"
81
+ OBJECT = "object"
82
+
83
+ scope = CharEnumField(ScopeEnum, default=ScopeEnum.PROJECT, verbose_name="作用域")
84
+
85
+ class Meta:
86
+ abstract = True
87
+
88
+
89
+ class SyncTimeModel(Model):
90
+ """add sync_time field to model"""
91
+
92
+ sync_time = DatetimeField(verbose_name="同步时间", blank=True, null=True)
93
+
94
+ class Meta:
95
+ abstract = True
96
+
97
+
98
+ class SyncCrontabModel(SyncTimeModel):
99
+ """add sync_crontab field to model"""
100
+
101
+ sync_crontab = DatetimeField(verbose_name="定时同步", null=True, blank=True)
102
+ sync_status = BooleanField(verbose_name="同步状态", default=False)
103
+ sync_error_message = TextField(verbose_name="同步错误", blank=True, null=True)
104
+
105
+ class Meta:
106
+ abstract = True
@@ -0,0 +1,53 @@
1
+ """
2
+ 自动发现 apps 目录下的 models 模块
3
+ """
4
+
5
+ from tortoise import Model
6
+ from faster_api.base import DiscoverBase
7
+
8
+
9
+ class ModelDiscover(DiscoverBase):
10
+ """模型发现器"""
11
+
12
+ INSTANCE_TYPE = Model
13
+
14
+ TARGETS = [
15
+ {
16
+ "directory": "apps",
17
+ "filename": "models.py",
18
+ "skip_dirs": ["__pycache__"],
19
+ "skip_files": [],
20
+ },
21
+ ]
22
+
23
+ def discover(self) -> dict[str, list[str]]:
24
+ """
25
+ 发现模型模块路径
26
+ 返回按app分组的模块路径字典,用于Tortoise ORM的apps配置
27
+ """
28
+ apps_models = {}
29
+
30
+ # 扫描 TARGETS 中的目录和文件
31
+ for target in self.TARGETS:
32
+ files = self.walk(
33
+ directory=target.get("directory"),
34
+ filename=target.get("filename"),
35
+ skip_files=target.get("skip_files"),
36
+ skip_dirs=target.get("skip_dirs"),
37
+ )
38
+
39
+ for file_path in files:
40
+ # 将文件路径转换为模块路径
41
+ # 例如: apps/auth/models.py -> apps.auth.models
42
+ module_path = file_path.replace("/", ".").replace(".py", "")
43
+
44
+ # 提取app名称 (例如: apps.auth.models -> auth)
45
+ path_parts = module_path.split(".")
46
+ if len(path_parts) >= 3 and path_parts[0] == "apps":
47
+ app_name = path_parts[1] # auth, perm, tenant, project
48
+
49
+ if app_name not in apps_models:
50
+ apps_models[app_name] = []
51
+ apps_models[app_name].append(module_path)
52
+
53
+ return apps_models
@@ -0,0 +1,13 @@
1
+ """
2
+ 路由管理模块
3
+
4
+ 提供了 FastAPI 路由的统一返回格式和自动发现功能
5
+ """
6
+
7
+ from .base import ApiResponse
8
+ from .discover import RoutesDiscover
9
+
10
+ __all__ = [
11
+ "ApiResponse",
12
+ "RoutesDiscover",
13
+ ]
@@ -0,0 +1,12 @@
1
+ """
2
+ 统一返回格式
3
+ """
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class ApiResponse(BaseModel):
9
+ """统一返回格式"""
10
+
11
+ message: str
12
+ data: dict
@@ -0,0 +1,3 @@
1
+ """
2
+ 内置路由模块
3
+ """
@@ -0,0 +1,12 @@
1
+ from fastapi import APIRouter
2
+ from settings import configs
3
+
4
+ router = APIRouter()
5
+
6
+
7
+ @router.get("/")
8
+ async def default():
9
+ return {
10
+ "message": f"Make {configs.PROJECT_NAME} Great Again",
11
+ "version": configs.VERSION,
12
+ }
@@ -0,0 +1,15 @@
1
+ from fastapi import APIRouter
2
+ from fastapi.openapi.docs import get_swagger_ui_html
3
+
4
+ router = APIRouter()
5
+
6
+
7
+ @router.get("/docs", include_in_schema=False)
8
+ async def custom_swagger_ui_html():
9
+ """自定义 Swagger UI 文档页面"""
10
+ return get_swagger_ui_html(
11
+ openapi_url="/openapi.json",
12
+ title="Auth Center - API 文档",
13
+ swagger_js_url="/static/swagger-ui-bundle.min.js",
14
+ swagger_css_url="/static/swagger-ui.min.css",
15
+ )
@@ -0,0 +1,52 @@
1
+ from fastapi import APIRouter
2
+ from faster_api.base import DiscoverBase
3
+
4
+
5
+ class RoutesDiscover(DiscoverBase):
6
+ INSTANCE_TYPE = APIRouter
7
+ TARGETS = [
8
+ {
9
+ "directory": "apps",
10
+ "filename": "routes.py",
11
+ "skip_dirs": ["__pycache__"],
12
+ "skip_files": [],
13
+ },
14
+ {
15
+ "directory": "routes/builtins",
16
+ "filename": None,
17
+ "skip_dirs": ["__pycache__"],
18
+ "skip_files": [],
19
+ },
20
+ ]
21
+
22
+ def import_and_extract_instances(
23
+ self, file_path: str, module_name: str
24
+ ) -> list[APIRouter]:
25
+ """
26
+ 导入模块并提取路由实例
27
+ 对于路由,我们查找已经实例化的 APIRouter 对象
28
+ """
29
+ instances = []
30
+
31
+ try:
32
+ # 动态导入模块
33
+ import importlib.util
34
+ import inspect
35
+
36
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
37
+ if spec is None or spec.loader is None:
38
+ return instances
39
+
40
+ module = importlib.util.module_from_spec(spec)
41
+ spec.loader.exec_module(module)
42
+
43
+ # 查找模块中所有的 APIRouter 实例
44
+ for _, obj in inspect.getmembers(module):
45
+ if isinstance(obj, self.INSTANCE_TYPE):
46
+ instances.append(obj)
47
+
48
+ except Exception as e:
49
+ # 静默跳过导入失败的模块,避免阻断整个发现过程
50
+ print(f"Warning: Failed to import routes from {module_name}: {e}")
51
+
52
+ return instances
@@ -0,0 +1,10 @@
1
+ """
2
+ 配置模块
3
+
4
+ 提供基于 pydantic-settings 的配置管理
5
+ """
6
+
7
+ from faster_api.settings.builtins.settings import DefaultSettings
8
+
9
+ # 创建默认配置实例
10
+ configs = DefaultSettings()
@@ -0,0 +1,64 @@
1
+ """
2
+ 应用配置文件
3
+ """
4
+
5
+ from typing import Optional
6
+ from pydantic_settings import BaseSettings
7
+
8
+
9
+ class DefaultSettings(BaseSettings):
10
+ """应用设置"""
11
+
12
+ # 基础配置
13
+ PROJECT_NAME: str = "Faster API"
14
+ VERSION: str = "0.1.0"
15
+ DEBUG: bool = True
16
+
17
+ # Server 配置
18
+ HOST: str = "0.0.0.0"
19
+ PORT: int = 8000
20
+
21
+ # API 配置
22
+ API_V1_STR: str = "/api/v1"
23
+
24
+ # JWT 配置
25
+ SECRET_KEY: str = "your-secret-key-here-change-in-production"
26
+ ALGORITHM: str = "HS256"
27
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
28
+
29
+ # 数据库配置
30
+ DB_ENGINE: str = "tortoise.backends.asyncpg"
31
+ DB_HOST: str = "localhost"
32
+ DB_PORT: int = 5432
33
+ DB_USER: str = "postgres"
34
+ DB_PASSWORD: str = "postgres"
35
+ DB_DATABASE: str = "faster_api"
36
+
37
+ TORTOISE_ORM: Optional[dict] = {
38
+ "connections": {
39
+ "development": {
40
+ "engine": "tortoise.backends.sqlite",
41
+ "credentials": {"file_path": "faster_api.db"},
42
+ },
43
+ "production": {
44
+ "engine": DB_ENGINE,
45
+ "credentials": {
46
+ "host": DB_HOST,
47
+ "port": DB_PORT,
48
+ "user": DB_USER,
49
+ "password": DB_PASSWORD,
50
+ "database": DB_DATABASE,
51
+ },
52
+ },
53
+ },
54
+ "apps": {
55
+ "models": {
56
+ # "models": ["apps.llm.models"], # 这里不要硬编码,由自动发现填充
57
+ "default_connection": "development" if DEBUG else "production",
58
+ }
59
+ },
60
+ }
61
+
62
+ class Config:
63
+ env_file = ".env"
64
+ exclude_from_env = {"TORTOISE_ORM"}
@@ -0,0 +1,79 @@
1
+ """
2
+ 自动发现 apps 目录下的 models 模块
3
+ """
4
+
5
+ from typing import Any, Dict
6
+ from pydantic_settings import BaseSettings
7
+ from faster_api.base import DiscoverBase
8
+
9
+
10
+ class SettingsDiscover(DiscoverBase):
11
+ """配置发现器"""
12
+
13
+ INSTANCE_TYPE = BaseSettings
14
+
15
+ TARGETS = [
16
+ {
17
+ "directory": "config",
18
+ "filename": None,
19
+ "skip_dirs": ["__pycache__"],
20
+ "skip_files": [],
21
+ },
22
+ {
23
+ "directory": "settings/builtins",
24
+ "filename": "settings.py",
25
+ "skip_dirs": ["__pycache__"],
26
+ "skip_files": [],
27
+ },
28
+ ]
29
+
30
+ def merge(self) -> BaseSettings:
31
+ """合并配置: 使用用户配置覆盖内置配置"""
32
+ default_settings = BaseSettings
33
+ user_settings = []
34
+
35
+ configs = self.discover()
36
+
37
+ for config in configs:
38
+ if type(config).__name__ == "DefaultSettings":
39
+ default_settings = config
40
+ else:
41
+ user_settings.append(config)
42
+
43
+ default_dict = default_settings.model_dump()
44
+
45
+ for user_setting in user_settings:
46
+ user_dict = user_setting.model_dump()
47
+ # 覆盖所有存在的属性
48
+ for key, value in user_dict.items():
49
+ default_dict[key] = value
50
+
51
+ # 为动态字段创建类型注解
52
+ annotations = {}
53
+ class_dict = {"__module__": __name__}
54
+
55
+ for key, value in default_dict.items():
56
+ # 根据值的类型推断注解
57
+ if isinstance(value, str):
58
+ annotations[key] = str
59
+ elif isinstance(value, int):
60
+ annotations[key] = int
61
+ elif isinstance(value, bool):
62
+ annotations[key] = bool
63
+ elif isinstance(value, dict):
64
+ annotations[key] = Dict[str, Any]
65
+ else:
66
+ annotations[key] = Any
67
+
68
+ # 设置默认值
69
+ class_dict[key] = value
70
+
71
+ # 添加类型注解
72
+ class_dict["__annotations__"] = annotations
73
+
74
+ # 动态创建 BaseSettings 的子类
75
+ MergedSettings = type("MergedSettings", (BaseSettings,), class_dict)
76
+
77
+ # 创建并返回实例
78
+ merged_settings = MergedSettings()
79
+ return merged_settings