fastapi-augment 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.
- fastapi_augment/__init__.py +24 -0
- fastapi_augment/common/__init__.py +61 -0
- fastapi_augment/common/constants.py +26 -0
- fastapi_augment/common/exception_handlers.py +178 -0
- fastapi_augment/common/exceptions.py +162 -0
- fastapi_augment/common/utils/__init__.py +5 -0
- fastapi_augment/common/utils/strings.py +175 -0
- fastapi_augment/config/__init__.py +8 -0
- fastapi_augment/config/settings.py +104 -0
- fastapi_augment/db/__init__.py +5 -0
- fastapi_augment/db/sqlalchemy/__init__.py +20 -0
- fastapi_augment/db/sqlalchemy/alembic/__init__.py +5 -0
- fastapi_augment/db/sqlalchemy/alembic/env.py +141 -0
- fastapi_augment/db/sqlalchemy/base.py +9 -0
- fastapi_augment/db/sqlalchemy/crud_base.py +426 -0
- fastapi_augment/db/sqlalchemy/engine.py +238 -0
- fastapi_augment/db/sqlalchemy/migrate.py +356 -0
- fastapi_augment/db/sqlalchemy/mixins/__init__.py +18 -0
- fastapi_augment/db/sqlalchemy/mixins/audit.py +61 -0
- fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +80 -0
- fastapi_augment/db/sqlalchemy/mixins/timestamp.py +48 -0
- fastapi_augment/db/sqlalchemy/model_base.py +47 -0
- fastapi_augment/db/sqlalchemy/session.py +160 -0
- fastapi_augment/factory.py +238 -0
- fastapi_augment/health/__init__.py +34 -0
- fastapi_augment/health/checker.py +101 -0
- fastapi_augment/health/checkers.py +109 -0
- fastapi_augment/health/router.py +87 -0
- fastapi_augment/lifespan.py +450 -0
- fastapi_augment/log/__init__.py +26 -0
- fastapi_augment/log/config.py +201 -0
- fastapi_augment/log/factory.py +32 -0
- fastapi_augment/log/filters.py +23 -0
- fastapi_augment/log/handlers.py +81 -0
- fastapi_augment/middlewares/__init__.py +20 -0
- fastapi_augment/middlewares/base.py +79 -0
- fastapi_augment/middlewares/request_id.py +82 -0
- fastapi_augment/openapi.py +110 -0
- fastapi_augment/py.typed +0 -0
- fastapi_augment/schemas/__init__.py +29 -0
- fastapi_augment/schemas/base.py +32 -0
- fastapi_augment/schemas/pagination.py +46 -0
- fastapi_augment/schemas/request.py +28 -0
- fastapi_augment/schemas/response.py +139 -0
- fastapi_augment/schemas/types.py +11 -0
- fastapi_augment-0.1.0.dist-info/METADATA +654 -0
- fastapi_augment-0.1.0.dist-info/RECORD +50 -0
- fastapi_augment-0.1.0.dist-info/WHEEL +5 -0
- fastapi_augment-0.1.0.dist-info/entry_points.txt +2 -0
- fastapi_augment-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@Author : zarkhan
|
|
3
|
+
@CreateDate : 2026/9/6
|
|
4
|
+
@Description: 基于 pydantic-settings 的环境配置管理
|
|
5
|
+
- 支持 .env 文件加载(路径由使用者显式指定)
|
|
6
|
+
- 支持环境变量前缀
|
|
7
|
+
- 支持嵌套配置(通过分隔符)
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, TypeVar
|
|
12
|
+
|
|
13
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
14
|
+
|
|
15
|
+
# SettingsConfigDict 所有可用键,用于区分配置参数和模型字段值
|
|
16
|
+
_CONFIG_KEYS = frozenset(SettingsConfigDict.__annotations__.keys())
|
|
17
|
+
|
|
18
|
+
_S = TypeVar('_S', bound='EnvSettings')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EnvSettings(BaseSettings):
|
|
22
|
+
"""环境配置基类,继承 pydantic_settings.BaseSettings。
|
|
23
|
+
|
|
24
|
+
自动从环境变量和 ``.env`` 文件加载配置,通过 ``from_env()`` 直接传参::
|
|
25
|
+
|
|
26
|
+
from fastapi_augment.config import EnvSettings
|
|
27
|
+
|
|
28
|
+
class Settings(EnvSettings):
|
|
29
|
+
database_url: str
|
|
30
|
+
redis_url: str = ''
|
|
31
|
+
debug: bool = False
|
|
32
|
+
|
|
33
|
+
# 直接传入 .env 路径、前缀等,无需导入 SettingsConfigDict
|
|
34
|
+
settings = Settings.from_env(
|
|
35
|
+
env_file='config/.env',
|
|
36
|
+
env_prefix='APP_',
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
特性:
|
|
40
|
+
- ``.env`` 文件路径由使用者显式指定,库不猜测项目根目录
|
|
41
|
+
- 支持 ``env_prefix`` 前缀过滤(如 ``APP_`` → ``APP_DATABASE_URL``)
|
|
42
|
+
- 支持 ``env_nested_delimiter`` 嵌套配置(如 ``DB__URL=xxx``)
|
|
43
|
+
- 环境变量优先级高于 .env 文件
|
|
44
|
+
- 默认 ``extra='ignore'``,未声明的环境变量被安全忽略
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
model_config = SettingsConfigDict(
|
|
48
|
+
env_prefix='',
|
|
49
|
+
env_file=None,
|
|
50
|
+
env_nested_delimiter=None,
|
|
51
|
+
case_sensitive=False,
|
|
52
|
+
extra='ignore',
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_env(cls: type[_S], **kwargs: Any) -> _S:
|
|
57
|
+
"""从环境加载配置,支持 ``SettingsConfigDict`` 所有参数。
|
|
58
|
+
|
|
59
|
+
内部自动区分 ``SettingsConfigDict`` 配置参数和模型字段值:
|
|
60
|
+
- 属于 ``SettingsConfigDict`` 的键 → 覆盖 ``model_config``
|
|
61
|
+
- 其余键 → 直接覆盖模型字段值(优先级最高)
|
|
62
|
+
|
|
63
|
+
常用配置参数:
|
|
64
|
+
env_file: .env 文件路径
|
|
65
|
+
env_prefix: 环境变量前缀(如 ``'APP_'``)
|
|
66
|
+
env_nested_delimiter: 嵌套配置分隔符(如 ``'__'``)
|
|
67
|
+
case_sensitive: 环境变量大小写敏感
|
|
68
|
+
env_file_encoding: .env 文件编码
|
|
69
|
+
secrets_dir: secrets 目录路径
|
|
70
|
+
json_file: JSON 配置文件路径
|
|
71
|
+
yaml_file: YAML 配置文件路径
|
|
72
|
+
toml_file: TOML 配置文件路径
|
|
73
|
+
cli_parse_args: 是否解析命令行参数
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
配置实例
|
|
77
|
+
|
|
78
|
+
Example::
|
|
79
|
+
|
|
80
|
+
settings = Settings.from_env(
|
|
81
|
+
env_file='config/.env',
|
|
82
|
+
env_prefix='APP_',
|
|
83
|
+
env_nested_delimiter='__',
|
|
84
|
+
debug=True, # 模型字段值覆盖
|
|
85
|
+
)
|
|
86
|
+
"""
|
|
87
|
+
config_overrides: dict[str, Any] = {}
|
|
88
|
+
field_overrides: dict[str, Any] = {}
|
|
89
|
+
|
|
90
|
+
for key, value in kwargs.items():
|
|
91
|
+
if key in _CONFIG_KEYS:
|
|
92
|
+
config_overrides[key] = str(value) if key == 'env_file' and value is not None else value
|
|
93
|
+
else:
|
|
94
|
+
field_overrides[key] = value
|
|
95
|
+
|
|
96
|
+
if config_overrides:
|
|
97
|
+
sub_cls = type(
|
|
98
|
+
f'{cls.__name__}__env',
|
|
99
|
+
(cls,),
|
|
100
|
+
{'model_config': SettingsConfigDict(**{**dict(cls.model_config), **config_overrides})},
|
|
101
|
+
)
|
|
102
|
+
return sub_cls(**field_overrides)
|
|
103
|
+
|
|
104
|
+
return cls(**field_overrides)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@Author : hangu
|
|
3
|
+
@CreateDate : 2026/8/31
|
|
4
|
+
@Description : SQLAlchemy integration — engine, session, model base, mixins, and CRUD base.
|
|
5
|
+
"""
|
|
6
|
+
from .base import Base
|
|
7
|
+
from .crud_base import CrudBase
|
|
8
|
+
from .engine import ClusterTopology, EngineManager, NodeConfig
|
|
9
|
+
from .model_base import ModelBase
|
|
10
|
+
from .session import SessionFactory
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'Base',
|
|
14
|
+
'CrudBase',
|
|
15
|
+
'ClusterTopology',
|
|
16
|
+
'EngineManager',
|
|
17
|
+
'NodeConfig',
|
|
18
|
+
'ModelBase',
|
|
19
|
+
'SessionFactory'
|
|
20
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import importlib
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from logging.config import fileConfig
|
|
5
|
+
from os import environ
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from alembic import context
|
|
9
|
+
from sqlalchemy import Connection, pool, engine_from_config
|
|
10
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
11
|
+
|
|
12
|
+
from fastapi_augment.db.sqlalchemy import Base
|
|
13
|
+
|
|
14
|
+
_logger = getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# this is the Alembic Config object, which provides
|
|
17
|
+
# access to the values within the .ini file in use.
|
|
18
|
+
config = context.config
|
|
19
|
+
|
|
20
|
+
# 从 alembic.ini 读取自定义版本表名称,如果没有则使用默认名
|
|
21
|
+
version_table = config.get_main_option('version_table', 'migration_version')
|
|
22
|
+
|
|
23
|
+
# Interpret the config file for Python logging.
|
|
24
|
+
# This line sets up loggers basically.
|
|
25
|
+
if config.config_file_name is not None:
|
|
26
|
+
fileConfig(config.config_file_name)
|
|
27
|
+
|
|
28
|
+
# add your model's MetaData object here
|
|
29
|
+
# for 'autogenerate' support
|
|
30
|
+
# from myapp import mymodel
|
|
31
|
+
# target_metadata = mymodel.Base.metadata
|
|
32
|
+
|
|
33
|
+
# 从环境变量 FASTAPI_AUGMENT_MODELS 动态导入用户模型模块,
|
|
34
|
+
# 使模型注册到 Base.metadata,autogenerate 才能检测到变更。
|
|
35
|
+
_models_env = environ.get('FASTAPI_AUGMENT_MODELS', '')
|
|
36
|
+
if _models_env:
|
|
37
|
+
for _mod in _models_env.split(','):
|
|
38
|
+
_mod = _mod.strip()
|
|
39
|
+
if _mod:
|
|
40
|
+
try:
|
|
41
|
+
importlib.import_module(_mod)
|
|
42
|
+
except ImportError as _e:
|
|
43
|
+
_logger.warning('无法导入模型模块 %s: %s', _mod, _e)
|
|
44
|
+
|
|
45
|
+
target_metadata = Base.metadata
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# other values from the config, defined by the needs of env.py,
|
|
49
|
+
# can be acquired:
|
|
50
|
+
# my_important_option = config.get_main_option("my_important_option")
|
|
51
|
+
# ... etc.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_migrations_offline() -> None:
|
|
55
|
+
"""Run alembic in 'offline' mode.
|
|
56
|
+
|
|
57
|
+
This configures the context with just a URL
|
|
58
|
+
and not an Engine, though an Engine is acceptable
|
|
59
|
+
here as well. By skipping the Engine creation
|
|
60
|
+
we don't even need a DBAPI to be available.
|
|
61
|
+
|
|
62
|
+
Calls to context.execute() here emit the given string to the
|
|
63
|
+
script output.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
url = config.get_main_option('sqlalchemy.url')
|
|
67
|
+
context.configure(
|
|
68
|
+
url=url,
|
|
69
|
+
target_metadata=target_metadata,
|
|
70
|
+
literal_binds=True,
|
|
71
|
+
dialect_opts={'paramstyle': 'named'},
|
|
72
|
+
version_table=version_table,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
with context.begin_transaction():
|
|
76
|
+
context.run_migrations()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def do_run_migrations(connection: Connection) -> None:
|
|
80
|
+
"""内部实际执行迁移的函数
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
connection: SQLAlchemy 数据库连接对象
|
|
84
|
+
"""
|
|
85
|
+
context.configure(
|
|
86
|
+
connection=connection,
|
|
87
|
+
target_metadata=target_metadata,
|
|
88
|
+
version_table=version_table
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
with context.begin_transaction():
|
|
92
|
+
context.run_migrations()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def run_async_migrations() -> None:
|
|
96
|
+
"""异步模式运行迁移
|
|
97
|
+
|
|
98
|
+
创建异步引擎并关联连接至上下文
|
|
99
|
+
"""
|
|
100
|
+
connectable = async_engine_from_config(
|
|
101
|
+
config.get_section(config.config_ini_section, {}),
|
|
102
|
+
prefix='sqlalchemy.',
|
|
103
|
+
poolclass=pool.NullPool,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async with connectable.connect() as connection:
|
|
107
|
+
await connection.run_sync(do_run_migrations)
|
|
108
|
+
|
|
109
|
+
await connectable.dispose()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run_migrations_online() -> None:
|
|
113
|
+
"""Run alembic in 'online' mode.
|
|
114
|
+
|
|
115
|
+
In this scenario we need to create an Engine
|
|
116
|
+
and associate a connection with the context.
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
url = config.get_main_option('sqlalchemy.url')
|
|
120
|
+
if not url:
|
|
121
|
+
raise ValueError('SQLAlchemy URL 未设置')
|
|
122
|
+
|
|
123
|
+
async_drivers = {'asyncpg', 'asyncmy', 'aiomysql', 'aiosqlite', 'aioodbc'}
|
|
124
|
+
is_async = urlparse(url).scheme.split('+')[-1] in async_drivers
|
|
125
|
+
|
|
126
|
+
if is_async:
|
|
127
|
+
asyncio.run(run_async_migrations())
|
|
128
|
+
else:
|
|
129
|
+
connectable = engine_from_config(
|
|
130
|
+
config.get_section(config.config_ini_section, {}),
|
|
131
|
+
prefix='sqlalchemy.',
|
|
132
|
+
poolclass=pool.NullPool,
|
|
133
|
+
)
|
|
134
|
+
with connectable.connect() as connection:
|
|
135
|
+
do_run_migrations(connection)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if context.is_offline_mode():
|
|
139
|
+
run_migrations_offline()
|
|
140
|
+
else:
|
|
141
|
+
run_migrations_online()
|