Files

196 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chongming-database
异步数据库封装,基于 `SQLModel`(SQLAlchemy),提供简洁的初始化与会话管理接口:
- **SQLite**:`aiosqlite` 驱动,内置多进程并发建表安全
- **PostgreSQL**:`asyncpg` 驱动,支持 URL / 配置字典两种接入方式
---
## 安装
```bash
uv pip install -e utils/database
```
---
## API
### `Database` 类
```python
from pathlib import Path
from chongming_database import Database
# SQLite(文件路径)
db = Database(Path("data/storage/chongming/app.db"))
await db.initialize() # 创建引擎 + 自动建表
# PostgreSQL(URL)
db = Database.from_url("postgresql+asyncpg://user:pwd@127.0.0.1:5432/mydb")
await db.initialize()
async with db.get_session() as session:
result = await session.execute(...)
await db.dispose() # 关闭连接
```
### 构造方式
| 方式 | 说明 |
| ------------------------- | ---------------------------------------------------------------------------- |
| `Database(db_path)` | SQLite 文件路径,自动创建父目录(向后兼容旧用法) |
| `Database(url=...)` | SQLAlchemy URL(PostgreSQL),`db_path` 与 `url` 必须且只能提供一个 |
| `Database.from_url(url)` | 同上,classmethod 写法 |
| `Database.from_config(c)` | 按配置字典创建,对齐 config 中 Database TypedDict 的契约(见下) |
### `from_config` 契约
与各处 config 中已有的 `Database` TypedDict 对齐:
```python
# SQLite
Database.from_config({"type": "sqlite", "database_path": "./data/app.db"})
# PostgreSQL(裸 postgresql:// 会自动归一化为 postgresql+asyncpg://)
Database.from_config({"type": "postgresql", "url": "postgresql://user:pwd@127.0.0.1:5432/mydb"})
```
`type="mysql"` 暂不支持,抛 `NotImplementedError`。
### 方法说明
| 方法 | 签名 | 说明 |
| ------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `__init__` | `(db_path=None, url=None)` | 二选一必填,否则抛 `ValueError`;`db_path` 自动创建父目录 |
| `initialize` | `(generate_schemas=True, echo=False, **kwargs)` | 创建异步引擎,`SQLModel.metadata.create_all` 自动建表(`generate_schemas=False` 可跳过,交由外部迁移工具),构建 `sessionmaker` |
> PostgreSQL:`initialize()` 检测到目标库不存在时(如全新 PG 实例),会以相同凭据连接
> `postgres` 维护库自动 `CREATE DATABASE`(多进程并发启动安全),因此无需在部署流程中
> 手工建库;要求连接账号具备 `CREATEDB` 权限(默认超级用户 `postgres` 满足)。
| `get_session` | `() -> AsyncSession` | 返回新的异步会话;未初始化则抛 `RuntimeError` |
| `dispose` | `async () -> None` | 关闭异步引擎并清除内部状态 |
### 多进程安全
`initialize()` 使用 `checkfirst=True` 检查表是否存在,并捕获 `OperationalError("already exists")` 作为兜底,确保多个进程并发启动时不会因建表冲突而崩溃。
- SQLite:DDL 自动提交,用 `connect()` 建表;PG:DDL 事务性,用 `begin()` 建表。
---
## 使用示例
### 配合 SQLModel 定义表
```python
from sqlmodel import SQLModel, Field
from datetime import datetime, timezone
class User(SQLModel, table=True):
id: str = Field(primary_key=True)
username: str = Field(max_length=50, unique=True, index=True)
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc)
)
```
### 完整生命周期
```python
from chongming_database import Database
from pathlib import Path
db = Database(Path("./data/myapp.db"))
await db.initialize()
try:
async with db.get_session() as session:
user = User(id="u1", username="john")
session.add(user)
await session.commit()
finally:
await db.dispose()
```
### PostgreSQL 本地开发(Docker)
```bash
docker run -d --name chongming-pg \
-e POSTGRES_PASSWORD=chongming -e POSTGRES_DB=chongming \
-p 5432:5432 postgres:16-alpine
# 连接
# postgresql+asyncpg://postgres:chongming@127.0.0.1:5432/chongming
```
### 插件中的典型用法
推荐通过 `Plugin` 基类自动建库:
- SQLite:`db_name` 指定文件名,`data_dir` 取自运行时配置(默认行为,现有插件零改动)
- PostgreSQL:在插件 `config.toml` 的 `[workers.config.database]` 下配置 `type`/`url`,
基类 `init_database()` 检测到 `url` 即走 `Database.from_config` 接入 PG
```toml
# config/workers/<name>/config.toml
[workers.config.database]
type = "postgresql"
url = "postgresql+asyncpg://postgres:chongming@127.0.0.1:5432/chongming"
generate_schemas = true
```
```python
# src/plugins/<name>/__init__.py
from chongming_worker.plugin import Plugin
class MyPlugin(Plugin):
type = "<name>"
# db_name 用于 SQLite;配置了 [workers.config.database].url 时优先走 PostgreSQL
async def on_init(self):
db = self.db # 已由基类 init_database() 初始化
self.set_var("db", db) # 发布到 global_var 供业务代码读取
...
# cleanup 时基类自动 dispose(无需手写)
```
不依赖基类时也可手动使用:
```python
from pathlib import Path
from chongming_database import Database
db_path = Path(config.get("data_dir", "./data")) / "<name>.db"
db = Database(db_path)
await db.initialize()
```
---
## 测试
```bash
uv sync --dev
# SQLite 单元测试(无需外部服务)
pytest tests/
# PostgreSQL 集成测试(需本地 PG,可通过上面的 docker 命令启动)
CHONGMING_TEST_PG_URL=postgresql+asyncpg://postgres:chongming@127.0.0.1:5432/chongming pytest tests/
```
未设置 `CHONGMING_TEST_PG_URL` 时 PG 测试自动跳过。
---
## 依赖
- `aiosqlite` — 异步 SQLite 驱动
- `asyncpg` — 异步 PostgreSQL 驱动
- `sqlmodel` — SQLAlchemy + Pydantic ORM
- `sqlalchemy` — 异步引擎支持