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.
- faster_api/__init__.py +58 -0
- faster_api/base.py +129 -0
- faster_api/commands/__init__.py +14 -0
- faster_api/commands/base.py +85 -0
- faster_api/commands/builtins/__init__.py +3 -0
- faster_api/commands/builtins/db.py +137 -0
- faster_api/commands/builtins/fastapi.py +87 -0
- faster_api/commands/discover.py +24 -0
- faster_api/db.py +32 -0
- faster_api/models/__init__.py +21 -0
- faster_api/models/base.py +106 -0
- faster_api/models/discover.py +53 -0
- faster_api/routes/__init__.py +13 -0
- faster_api/routes/base.py +12 -0
- faster_api/routes/builtins/__init__.py +3 -0
- faster_api/routes/builtins/defaults.py +12 -0
- faster_api/routes/builtins/swagger.py +15 -0
- faster_api/routes/discover.py +52 -0
- faster_api/settings/__init__.py +10 -0
- faster_api/settings/builtins/settings.py +64 -0
- faster_api/settings/discover.py +79 -0
- faster_api/statics/swagger-ui-bundle.min.js +11 -0
- faster_api/statics/swagger-ui.min.css +1 -0
- faster_app-0.1.0.dist-info/METADATA +285 -0
- faster_app-0.1.0.dist-info/RECORD +28 -0
- faster_app-0.1.0.dist-info/WHEEL +5 -0
- faster_app-0.1.0.dist-info/licenses/LICENSE +21 -0
- faster_app-0.1.0.dist-info/top_level.txt +1 -0
@@ -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,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,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
|