# async-pybatis-orm
一个基于 MySQL 异步场景,对齐 MyBatis-Plus 语法风格的 Python ORM 框架。
专注于易用性与可扩展性,为从 Java MyBatis-Plus 转过来的开发者提供熟悉的 API。
## ✨ 特性
- 🚀 **异步支持**: 基于 asyncio 和 aiomysql,提供高性能异步操作
- 🎯 **MyBatis-Plus 风格**: 熟悉的 API 设计,降低学习成本
- 🔧 **灵活查询**: 链式条件构造器,支持复杂查询
- 📦 **完整 CRUD**: 提供 Insert、Update、Delete、Select 等完整操作
- 📄 **分页支持**: 内置分页功能,简单易用
- 🔒 **类型安全**: 支持 Python 类型注解
## 📦 安装
使用 pip 安装:
```bash
pip install async-pybatis-orm
```
或者从源码安装:
```bash
git clone https://github.com/lipop/async-pybatis-orm.git
cd async-pybatis-orm
pip install -e .
```
## 🚀 快速开始
### 1. 定义模型
```python
from datetime import datetime
from typing import Optional
from databases import Database
from async_pybatis_orm import CRUDModel, QueryWrapper
from async_pybatis_orm.base.database_manager import DatabaseAdapter
from async_pybatis_orm.fields import Field
# 定义数据库适配器
class DatabaseManager:
_adapter: DatabaseAdapter = None
@classmethod
async def initialize(cls, database_url: str):
database = Database(database_url)
await database.connect()
cls._adapter = DatabaseAdapter(database)
return cls._adapter
# 定义模型
class User(CRUDModel):
__table_meta__ = {"table_name": "users", "primary_key": "id"}
id: int = Field(column_name="id")
username: str = Field(column_name="user_name")
email: Optional[str] = Field(column_name="email", nullable=True)
age: Optional[int] = Field(column_name="age", nullable=True)
status: str = Field(column_name="status", default="active")
created_at: datetime = Field(column_name="created_at", default_factory=datetime.now)
@classmethod
async def _fetch_all(cls, sql: str, params: dict = None):
cls._database = DatabaseManager._adapter
return await cls._database.fetch_all(sql, params or {})
@classmethod
async def _execute(cls, sql: str, params):
cls._database = DatabaseManager._adapter
return await cls._database.execute(sql, params or {})
```
### 2. 初始化数据库连接
```python
import asyncio
async def init():
db_url = "mysql+aiomysql://user:password@localhost:3306/dbname?min_size=5&max_size=20"
await DatabaseManager.initialize(db_url)
asyncio.run(init())
```
### 3. 使用 CRUD 操作
```python
# 插入数据
user = User()
user.username = "test"
user.email = "test@example.com"
user.age = 18
await User.insert(user)
# 批量插入
users = [User(username=f"user{i}", age=20+i) for i in range(5)]
await User.batch_save(users)
# 查询数据
users = await User.select_list()
user = await User.select_by_id(1)
count = await User.select_count()
# 条件查询
wrapper = QueryWrapper().eq(User.username, "test").gt(User.age, 18)
users = await User.select_list(wrapper)
# 分页查询
from async_pybatis_orm.pagination import Page
page = Page(current=1, size=10)
result = await User.select_page(page)
# 更新数据
update_user = User()
update_user.id = 1
update_user.age = 25
await User.update_by_id(update_user)
# 条件更新
from async_pybatis_orm.wrapper import UpdateWrapper
await User.update_by_wrapper(
UpdateWrapper()
.eq(User.username, "test")
.set(User.age, 25)
)
# 删除数据
await User.remove_by_id(1)
# 条件删除
wrapper = QueryWrapper().eq(User.status, "inactive")
await User.remove_by_wrapper(wrapper)
```
### 4. 链式查询
```python
# 复杂条件查询
wrapper = QueryWrapper() \
.eq(User.status, "active") \
.like(User.username, "admin") \
.in_(User.age, [18, 19, 20]) \
.between(User.created_at, "2023-01-01", "2023-12-31") \
.order_by(User.id, desc=True) \
.limit(10)
users = await User.select_list(wrapper)
```
## 📚 更多示例
查看 `example.py` 文件获取完整的测试示例。
## 🔧 开发
### 构建发布包
```bash
pip install build
python -m build
```
### 上传到 PyPI
```bash
pip install twine
twine upload dist/*
```
## 📄 许可证
MIT License
## 👤 作者
lipop
Raw data
{
"_id": null,
"home_page": null,
"name": "async-pybatis-orm",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "async, orm, mysql, database, aiomysql, asyncio, mybatis-plus, mapper, crud, query, pagination",
"author": "lipop",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/64/02/2bfcdbad52f8f27644b912848cb8758fe307b86ac4662a71984f4ea0d5b4/async_pybatis_orm-1.0.0.tar.gz",
"platform": null,
"description": "# async-pybatis-orm\r\n\r\n\u4e00\u4e2a\u57fa\u4e8e MySQL \u5f02\u6b65\u573a\u666f\uff0c\u5bf9\u9f50 MyBatis-Plus \u8bed\u6cd5\u98ce\u683c\u7684 Python ORM \u6846\u67b6\u3002\r\n\r\n\u4e13\u6ce8\u4e8e\u6613\u7528\u6027\u4e0e\u53ef\u6269\u5c55\u6027\uff0c\u4e3a\u4ece Java MyBatis-Plus \u8f6c\u8fc7\u6765\u7684\u5f00\u53d1\u8005\u63d0\u4f9b\u719f\u6089\u7684 API\u3002\r\n\r\n## \u2728 \u7279\u6027\r\n\r\n- \ud83d\ude80 **\u5f02\u6b65\u652f\u6301**: \u57fa\u4e8e asyncio \u548c aiomysql\uff0c\u63d0\u4f9b\u9ad8\u6027\u80fd\u5f02\u6b65\u64cd\u4f5c\r\n- \ud83c\udfaf **MyBatis-Plus \u98ce\u683c**: \u719f\u6089\u7684 API \u8bbe\u8ba1\uff0c\u964d\u4f4e\u5b66\u4e60\u6210\u672c\r\n- \ud83d\udd27 **\u7075\u6d3b\u67e5\u8be2**: \u94fe\u5f0f\u6761\u4ef6\u6784\u9020\u5668\uff0c\u652f\u6301\u590d\u6742\u67e5\u8be2\r\n- \ud83d\udce6 **\u5b8c\u6574 CRUD**: \u63d0\u4f9b Insert\u3001Update\u3001Delete\u3001Select \u7b49\u5b8c\u6574\u64cd\u4f5c\r\n- \ud83d\udcc4 **\u5206\u9875\u652f\u6301**: \u5185\u7f6e\u5206\u9875\u529f\u80fd\uff0c\u7b80\u5355\u6613\u7528\r\n- \ud83d\udd12 **\u7c7b\u578b\u5b89\u5168**: \u652f\u6301 Python \u7c7b\u578b\u6ce8\u89e3\r\n\r\n## \ud83d\udce6 \u5b89\u88c5\r\n\r\n\u4f7f\u7528 pip \u5b89\u88c5\uff1a\r\n\r\n```bash\r\npip install async-pybatis-orm\r\n```\r\n\r\n\u6216\u8005\u4ece\u6e90\u7801\u5b89\u88c5\uff1a\r\n\r\n```bash\r\ngit clone https://github.com/lipop/async-pybatis-orm.git\r\ncd async-pybatis-orm\r\npip install -e .\r\n```\r\n\r\n## \ud83d\ude80 \u5feb\u901f\u5f00\u59cb\r\n\r\n### 1. \u5b9a\u4e49\u6a21\u578b\r\n\r\n```python\r\nfrom datetime import datetime\r\nfrom typing import Optional\r\nfrom databases import Database\r\nfrom async_pybatis_orm import CRUDModel, QueryWrapper\r\nfrom async_pybatis_orm.base.database_manager import DatabaseAdapter\r\nfrom async_pybatis_orm.fields import Field\r\n\r\n# \u5b9a\u4e49\u6570\u636e\u5e93\u9002\u914d\u5668\r\nclass DatabaseManager:\r\n _adapter: DatabaseAdapter = None\r\n\r\n @classmethod\r\n async def initialize(cls, database_url: str):\r\n database = Database(database_url)\r\n await database.connect()\r\n cls._adapter = DatabaseAdapter(database)\r\n return cls._adapter\r\n\r\n# \u5b9a\u4e49\u6a21\u578b\r\nclass User(CRUDModel):\r\n __table_meta__ = {\"table_name\": \"users\", \"primary_key\": \"id\"}\r\n\r\n id: int = Field(column_name=\"id\")\r\n username: str = Field(column_name=\"user_name\")\r\n email: Optional[str] = Field(column_name=\"email\", nullable=True)\r\n age: Optional[int] = Field(column_name=\"age\", nullable=True)\r\n status: str = Field(column_name=\"status\", default=\"active\")\r\n created_at: datetime = Field(column_name=\"created_at\", default_factory=datetime.now)\r\n\r\n @classmethod\r\n async def _fetch_all(cls, sql: str, params: dict = None):\r\n cls._database = DatabaseManager._adapter\r\n return await cls._database.fetch_all(sql, params or {})\r\n\r\n @classmethod\r\n async def _execute(cls, sql: str, params):\r\n cls._database = DatabaseManager._adapter\r\n return await cls._database.execute(sql, params or {})\r\n```\r\n\r\n### 2. \u521d\u59cb\u5316\u6570\u636e\u5e93\u8fde\u63a5\r\n\r\n```python\r\nimport asyncio\r\n\r\nasync def init():\r\n db_url = \"mysql+aiomysql://user:password@localhost:3306/dbname?min_size=5&max_size=20\"\r\n await DatabaseManager.initialize(db_url)\r\n\r\nasyncio.run(init())\r\n```\r\n\r\n### 3. \u4f7f\u7528 CRUD \u64cd\u4f5c\r\n\r\n```python\r\n# \u63d2\u5165\u6570\u636e\r\nuser = User()\r\nuser.username = \"test\"\r\nuser.email = \"test@example.com\"\r\nuser.age = 18\r\nawait User.insert(user)\r\n\r\n# \u6279\u91cf\u63d2\u5165\r\nusers = [User(username=f\"user{i}\", age=20+i) for i in range(5)]\r\nawait User.batch_save(users)\r\n\r\n# \u67e5\u8be2\u6570\u636e\r\nusers = await User.select_list()\r\nuser = await User.select_by_id(1)\r\ncount = await User.select_count()\r\n\r\n# \u6761\u4ef6\u67e5\u8be2\r\nwrapper = QueryWrapper().eq(User.username, \"test\").gt(User.age, 18)\r\nusers = await User.select_list(wrapper)\r\n\r\n# \u5206\u9875\u67e5\u8be2\r\nfrom async_pybatis_orm.pagination import Page\r\npage = Page(current=1, size=10)\r\nresult = await User.select_page(page)\r\n\r\n# \u66f4\u65b0\u6570\u636e\r\nupdate_user = User()\r\nupdate_user.id = 1\r\nupdate_user.age = 25\r\nawait User.update_by_id(update_user)\r\n\r\n# \u6761\u4ef6\u66f4\u65b0\r\nfrom async_pybatis_orm.wrapper import UpdateWrapper\r\nawait User.update_by_wrapper(\r\n UpdateWrapper()\r\n .eq(User.username, \"test\")\r\n .set(User.age, 25)\r\n)\r\n\r\n# \u5220\u9664\u6570\u636e\r\nawait User.remove_by_id(1)\r\n\r\n# \u6761\u4ef6\u5220\u9664\r\nwrapper = QueryWrapper().eq(User.status, \"inactive\")\r\nawait User.remove_by_wrapper(wrapper)\r\n```\r\n\r\n### 4. \u94fe\u5f0f\u67e5\u8be2\r\n\r\n```python\r\n# \u590d\u6742\u6761\u4ef6\u67e5\u8be2\r\nwrapper = QueryWrapper() \\\r\n .eq(User.status, \"active\") \\\r\n .like(User.username, \"admin\") \\\r\n .in_(User.age, [18, 19, 20]) \\\r\n .between(User.created_at, \"2023-01-01\", \"2023-12-31\") \\\r\n .order_by(User.id, desc=True) \\\r\n .limit(10)\r\n\r\nusers = await User.select_list(wrapper)\r\n```\r\n\r\n## \ud83d\udcda \u66f4\u591a\u793a\u4f8b\r\n\r\n\u67e5\u770b `example.py` \u6587\u4ef6\u83b7\u53d6\u5b8c\u6574\u7684\u6d4b\u8bd5\u793a\u4f8b\u3002\r\n\r\n## \ud83d\udd27 \u5f00\u53d1\r\n\r\n### \u6784\u5efa\u53d1\u5e03\u5305\r\n\r\n```bash\r\npip install build\r\npython -m build\r\n```\r\n\r\n### \u4e0a\u4f20\u5230 PyPI\r\n\r\n```bash\r\npip install twine\r\ntwine upload dist/*\r\n```\r\n\r\n## \ud83d\udcc4 \u8bb8\u53ef\u8bc1\r\n\r\nMIT License\r\n\r\n## \ud83d\udc64 \u4f5c\u8005\r\n\r\nlipop\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "\u4e00\u4e2a\u57fa\u4e8e MySQL \u5f02\u6b65\u573a\u666f\uff0c\u5bf9\u9f50 MyBatis-Plus \u8bed\u6cd5\u98ce\u683c\u7684 Python ORM \u6846\u67b6",
"version": "1.0.0",
"project_urls": null,
"split_keywords": [
"async",
" orm",
" mysql",
" database",
" aiomysql",
" asyncio",
" mybatis-plus",
" mapper",
" crud",
" query",
" pagination"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6a9f8e58630d3b3057f4b458a86483cce79064b8c36b79505b8996d9df11b471",
"md5": "597b0389517d027e3fa790ca434505e5",
"sha256": "ea58a219ed194210cab7045162dc46290d6906189c18621eb89f07309b36675d"
},
"downloads": -1,
"filename": "async_pybatis_orm-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "597b0389517d027e3fa790ca434505e5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 40133,
"upload_time": "2025-11-02T03:56:53",
"upload_time_iso_8601": "2025-11-02T03:56:53.989928Z",
"url": "https://files.pythonhosted.org/packages/6a/9f/8e58630d3b3057f4b458a86483cce79064b8c36b79505b8996d9df11b471/async_pybatis_orm-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "64022bfcdbad52f8f27644b912848cb8758fe307b86ac4662a71984f4ea0d5b4",
"md5": "7fa2f8fe740e021e66f42f16517d18e3",
"sha256": "1c3a8a85e20256a1f4ce4eb978c83a4c47643e9c233b4a11d3f65ff35447f493"
},
"downloads": -1,
"filename": "async_pybatis_orm-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "7fa2f8fe740e021e66f42f16517d18e3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 26947,
"upload_time": "2025-11-02T03:56:55",
"upload_time_iso_8601": "2025-11-02T03:56:55.587040Z",
"url": "https://files.pythonhosted.org/packages/64/02/2bfcdbad52f8f27644b912848cb8758fe307b86ac4662a71984f4ea0d5b4/async_pybatis_orm-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-02 03:56:55",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "async-pybatis-orm"
}