Metadata-Version: 2.4
Name: pyre-sqlalchemy-db
Version: 1.0.0
Summary: SQLAlchemy 2.0 声明式 ORM 增强库：字段、事务、分页、软删除与异步支持
Home-page: https://github.com/breezechime/sqlalchemy-db
Author: 且听风吟、我是指针*、ZDLAY、KYZ
Author-email: breezechime@163.com
License: MIT
Project-URL: Source, https://github.com/breezechime/sqlalchemy-db
Project-URL: Issues, https://github.com/breezechime/sqlalchemy-db/issues
Keywords: sqlalchemy orm async transaction pagination snowflake flask starlette
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Framework :: Flask
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: SQLAlchemy>=2.0.4
Requires-Dist: pydantic>=2.0
Provides-Extra: async
Requires-Dist: greenlet; extra == "async"
Requires-Dist: aiosqlite; extra == "async"
Provides-Extra: mysql
Requires-Dist: pymysql; extra == "mysql"
Provides-Extra: async-mysql
Requires-Dist: aiomysql; extra == "async-mysql"
Provides-Extra: postgres
Requires-Dist: psycopg2-binary; extra == "postgres"
Provides-Extra: async-postgres
Requires-Dist: asyncpg; extra == "async-postgres"
Provides-Extra: flask
Requires-Dist: Flask; extra == "flask"
Provides-Extra: all
Requires-Dist: greenlet; extra == "all"
Requires-Dist: aiosqlite; extra == "all"
Requires-Dist: pymysql; extra == "all"
Requires-Dist: aiomysql; extra == "all"
Requires-Dist: asyncpg; extra == "all"
Requires-Dist: Flask; extra == "all"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# pyre-sqlalchemy-db

<p>
  <img src="https://img.shields.io/badge/Python-3.9%2B-3776AB?logo=python&logoColor=white" alt="Python">
  <img src="https://img.shields.io/badge/SQLAlchemy-2.0-d71f00?logo=sqlalchemy&logoColor=white" alt="SQLAlchemy 2.0">
  <img src="https://img.shields.io/badge/license-MIT-blue" alt="License">
  <img src="https://img.shields.io/badge/async-supported-2ea043" alt="Async">
</p>

基于 **SQLAlchemy 2.0** 的轻量 ORM 增强库：声明式字段、事务管理、分页、软删除、雪花 ID
以及完整的异步支持，并与 Flask / Starlette 等框架无侵入集成。

## ✨ 特点

- 🧱 **声明式字段** — `StringField`、`DateTimeField`、`ForeignKeyField` 开箱即用，参数透传 `mapped_column`。
- 🔁 **事务管理** — `transactional` / `local_transaction`，支持 `REQUIRED` / `NEW` / `MANDATORY` 传播与隔离级别。
- ⚡ **异步支持** — `async_transactional` / `Model.async_query`，同一套模型同步、异步通用。
- 🧩 **常用 Mixin** — 主键、创建/更新时间、软删除、操作人，按需组合。
- 🆔 **主键策略** — 自增、雪花 ID、UUID 三种，一行切换。
- 🔌 **多数据源** — 按 schema 绑定多个引擎，模型自动路由。
- 📤 **序列化** — `to_dict()` 内置循环引用保护与日期 / Decimal / 枚举编码。
- 🪶 **框架无关** — 核心不依赖任何 Web 框架，可选接入 Flask 应用上下文。

## 📦 安装

```bash
pip install pyre-sqlalchemy-db
```

按需安装可选依赖：

```bash
pip install "pyre-sqlalchemy-db[async]"   # greenlet / aiosqlite / aiomysql / asyncpg
pip install "pyre-sqlalchemy-db[mysql]"
pip install "pyre-sqlalchemy-db[flask]"
```

## 💡 一个简单的例子

```python
from sqlalchemy_db import SQLAlchemyDB, models

db = SQLAlchemyDB("sqlite:///demo.db")


class User(models.PkMixin, models.TimestampMixin, models.Model):
    __tablename__ = "user"

    name = models.StringField(50, comment="用户名")
    age = models.IntegerField(default=0)
```

字段、模型与 Mixin 统一从 `models` 命名空间取用（`models` 即 `sqlalchemy_db.models` 子包）。

## 👩‍💻 目录结构

```text
sqlalchemy-db
├── sqlalchemy_db
│   ├── __init__.py           # 对外导出
│   ├── app.py                # 框架集成入口
│   ├── transaction.py        # 同步事务
│   ├── async_transaction.py  # 异步事务
│   ├── snowflake.py          # 雪花 ID 生成
│   ├── encoders.py           # 序列化编码器
│   ├── exceptions.py         # 异常体系
│   ├── models/               # Model / 字段 / Mixin / 查询
│   ├── globals.py            # 实例解析
│   ├── constants.py
│   └── utils.py
├── examples/                 # 可运行示例
├── tests/                    # 单元测试
└── setup.py
```

## 🚀 快速开始

```python
from sqlalchemy_db import SQLAlchemyDB, Page, local_transaction

db = SQLAlchemyDB("sqlite:///demo.db")
db.create_all()

# 写入
with local_transaction():
    user = User(name="Alice", age=18)
    user.insert()
    print(user.id)          # 1

# 更新与删除
with local_transaction():
    user.save()
    user.delete()

# 查询与分页
with local_transaction():
    User.query.get_by_id_or_404(1)
    User.query.select(User.age >= 18, order_by=User.id.desc())

    page = Page(page=1, page_size=20)
    User.query.select_page(page)
    page.get_data()             # 数据列表
    page.get_count()            # 总条数
```

> `Model.query` 在访问时即从当前会话上下文取 Session，因此所有查询都要运行在
> `local_transaction()` / `transactional` 上下文内。否则库会告警事务上下文不会自动回收。

## 🧱 模型

### 字段

| 字段 | 对应类型 |
| --- | --- |
| `StringField(255)` | `String` |
| `TextField()` / `MediumtextField()` | `Text` / `MEDIUMTEXT` |
| `IntegerField` / `BigIntegerField` / `SmallIntegerField` | 整型 |
| `FloatField` / `NumericField` / `DecimalField` | 浮点 / 精确数值 |
| `BooleanField()` | `Boolean` |
| `DateField` / `DateTimeField` / `TimeField` | 日期时间 |
| `JSONField()` | `JSON` |
| `BinaryField()` | `LargeBinary` |
| `EnumField` / `IntEnumField` / `StrEnumField` | 枚举 |
| `ForeignKeyField("user.id")` | 外键 |
| `AutoPkField(pk_type=...)` | 主键（`AUTO` / `SNOWFLAKE` / `UUID`） |

所有字段均透传 `mapped_column` 参数，`nullable`、`comment`、`server_default`、`index`、
`unique`、`sort_order` 照常可用。

### Mixin

| Mixin | 说明 |
| --- | --- |
| `PkMixin` | 提供 `id` 主键 |
| `TimestampMixin` | 自动维护 `create_time` / `update_time` |
| `SoftDeleteMixin` | 提供 `delete_time` 列与软删除查询支持 |
| `Model` | 基础模型，内置实例级 `insert` / `save` / `delete` |

### 主键策略

```python
from sqlalchemy_db import PkType, models


class Order(models.Model):
    __tablename__ = "order"
    id = models.AutoPkField(pk_type=PkType.SNOWFLAKE)
```

### 序列化

```python
user.to_dict()
user.to_dict(exclude={"age"}, exclude_none=True)

User.from_dict({"name": "Bob", "age": 20})
User.from_entity(pydantic_model)
```

## 🔍 查询

`Model.query` / `Model.async_query` 提供统一的链式查询入口。查询须在会话上下文内执行：

```python
with local_transaction():
    User.query.get_by_id(1)                       # 按主键
    User.query.get_by_id_or_404(1)                # 不存在抛 NotFoundDataException
    User.query.get_one(User.name == "Alice")
    User.query.select(User.age >= 18, order_by=User.id.desc())
    User.query.select_batch_ids([1, 2, 3])
    User.query.select_count(User.is_active.is_(True))
    User.query.delete_by_id_or_404(1)
```

软删除：

```python
with local_transaction():
    User.query.with_not_trashed().all()   # 排除已删除
    User.query.only_trashed().all()       # 仅已删除
    User.query.with_trashed().all()       # 全部
```

## 🔁 事务

```python
from sqlalchemy_db import transactional, local_transaction, Propagation, Isolation


@transactional
def create_user(name: str):
    User(name=name).insert()


with local_transaction():
    User(name="Bob").insert()


@transactional(propagation=Propagation.NEW, isolation=Isolation.READ_COMMITTED, autocommit=True)
def task():
    ...
```

## ⚡ 异步

模型无需改动，异步场景使用 `async_transactional` 与 `Model.async_query`：

```python
from sqlalchemy_db import SQLAlchemyDB, async_transactional

db = SQLAlchemyDB("sqlite+aiosqlite:///demo.db")


@async_transactional
async def main():
    user = await User.async_query.get_by_id(1)
    rows = await User.async_query.select(User.age >= 18)
    page = await User.async_query.select_page(Page(page=1, page_size=20))
```

## ⚙️ 配置

```python
db = SQLAlchemyDB({
    "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123456@127.0.0.1:3306/demo",
    "SQLALCHEMY_ECHO": False,
    "SQLALCHEMY_ENGINE_OPTIONS": {"pool_size": 10, "pool_recycle": 3600},
    "SQLALCHEMY_ASYNC_MODE": "auto",          # auto / sync / async
    "SNOWFLAKE_DATACENTER_ID": 1,
    "SNOWFLAKE_WORKER_ID": 1,
})
```

| 配置项 | 说明 |
| --- | --- |
| `SQLALCHEMY_DATABASE_URI` | 主库连接串 |
| `SQLALCHEMY_ENGINE_OPTIONS` | 引擎参数 |
| `SQLALCHEMY_SESSION_OPTIONS` | Session 参数 |
| `SQLALCHEMY_ECHO` | 打印 SQL |
| `SQLALCHEMY_ASYNC_MODE` | `auto` / `sync` / `async` |
| `SQLALCHEMY_SCHEMAS` | 多数据源，`{schema: url}` |
| `SNOWFLAKE_DATACENTER_ID` / `SNOWFLAKE_WORKER_ID` / `SNOWFLAKE_EPOCH` | 雪花 ID 配置 |

## 🔌 多数据源

```python
from sqlalchemy_db import models


class Log(models.Model):
    __tablename__ = "log"
    __schema__ = "log"        # 路由到 "log" 数据源
```

```python
db = SQLAlchemyDB({
    "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://.../main",
    "SQLALCHEMY_SCHEMAS": {"log": "mysql+pymysql://.../logs"},
})
```

## 🧩 Flask 集成

```python
from flask import Flask
from sqlalchemy_db import SQLAlchemyDB

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:123456@127.0.0.1:3306/demo"

db = SQLAlchemyDB(app)


@app.route("/users/<int:uid>")
def get_user(uid):
    return db.session.get(User, uid).to_dict()
```

位于 Flask 应用上下文内时，`Model.query` 等入口会自动解析当前应用绑定的实例。

## 📚 示例

更多可运行示例见 [`examples/`](examples)。

## 📄 许可

本项目基于 [MIT](LICENSE) 许可开源。
