Skip to content

Commit

Permalink
feat #30 支持tortoise orm
Browse files Browse the repository at this point in the history
  • Loading branch information
zy7y committed Apr 27, 2024
1 parent 1afae65 commit 1556e3a
Show file tree
Hide file tree
Showing 19 changed files with 505 additions and 69 deletions.
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
![dfs-generate](image/logo.png)
# dfs-generate
通过已有数据库表,生成FastAPI接口的工具项目,最终目的为FastAPI使用者,减少代码; 项目启发 Mybatis 逆向工程、[pdmaner](https://gitee.com/robergroup/pdmaner)

- d -> db 数据库
- f -> fastapi + uvicorn 接口服务
- s -> sqlmodel 数据实体、ORM模型
[桌面端下载](https://github.com/zy7y/dfs-generate/releases)
# 支持ORM
- [x] SQLModel
- [x] Tortoise ORM

# Generate Code
[FastAPI SQLModel MySQL](docs/sqlmodel)
![](docs/sqlmodel/api.png)

[FastAPI Tortoise ORM MySQL](docs/tortoise-orm)
![](docs/tortoise-orm/api.png)

> 使用过程中有疑问、或其他宝贵意见 -> [issues](https://github.com/zy7y/dfs-generate/issues),如果你对这个项目感兴趣,欢迎加入共同实现,
# 为什么不是其他
> 个人观点
1. 目前[Tortoise ORM](https://tortoise.github.io/)[Django Ninja](https://django-ninja.dev/)通过模型直接生成对应的Pydantic模型,均采用的 [动态创建模型](https://docs.pydantic.dev/latest/concepts/models/#dynamic-model-creation),在Pycharm`.属性`无代码提示。
2. [SQLModel](https://github.com/tiangolo/sqlmodel/issues/654) 已有表生成模型,模型生成表,官方还未提供。
3. [fastapi-crudrouter](https://fastapi-crudrouter.awtkns.com/) 内部通过add_router方式注册路由,虽然代码减少了,但是灵活性变低了
4. 或该更多的使用表之间[逻辑关联](https://www.zhihu.com/question/20006142)
## Star History
<!-- STAR_HISTORY -->
Expand Down
35 changes: 32 additions & 3 deletions dfs_generate/conversion.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from string import Template

from tpl import SQLMODEL_DAO, TORTOISE_DAO, RESPONSE_SCHEMA, SQLMODEL_ROUTER, MAIN
from templates import (
SQLMODEL_DAO,
TORTOISE_DAO,
RESPONSE_SCHEMA,
SQLMODEL_ROUTER,
SQLMODEL_MAIN,
TORTOISE_MAIN,
TORTOISE_ROUTER,
SQLMODEL_DB,
)
from tools import to_pascal, tran, to_snake


Expand All @@ -21,9 +30,10 @@ def _pydantic_field(column, imports):


class Conversion:
def __init__(self, table_name, columns):
def __init__(self, table_name, columns, uri):
self.table_name = table_name
self.columns = columns
self.uri = uri

@property
def table(self):
Expand Down Expand Up @@ -70,7 +80,7 @@ def router(self):
pass

def main(self):
return MAIN.format(router_name=self.router_name)
pass

def gencode(self):
return {
Expand Down Expand Up @@ -176,6 +186,14 @@ def router(self):
router_name=self.router_name, table=self.table
)

def main(self):
return SQLMODEL_MAIN.format(router_name=self.router_name)

def gencode(self):
data = super().gencode()
data["db.py"] = SQLMODEL_DB.format(uri=self.uri)
return data


def _tortoise_field_repr(column):
name = column["COLUMN_NAME"]
Expand Down Expand Up @@ -237,3 +255,14 @@ def dao(self):
imports = {"from typing import List, Optional", "import model", "import schema"}
content = TORTOISE_DAO.format(table=self.table)
return "\n".join(imports) + "\n\n" + content

def main(self):
self.uri = self.uri.replace("+pymysql", "")
return Template(TORTOISE_MAIN).safe_substitute(
router_name=self.router_name, uri=self.uri
)

def router(self):
return Template(TORTOISE_ROUTER).safe_substitute(
router_name=self.router_name, table=self.table
)
30 changes: 11 additions & 19 deletions dfs_generate/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from conversion import SQLModelConversion, TortoiseConversion
from tools import MySQLConf, MySQLHelper
from tpl import SQLMODEL_DB

app = bottle.Bottle()

Expand Down Expand Up @@ -75,25 +74,18 @@ def codegen():
mode = bottle.request.query.get("mode")
results = []
if mode == "sqlmodel":
data = SQLModelConversion(table, obj.get_table_columns(table)).gencode()

for k, v in data.items():
_code = FormatCode(v, style_config="pep8")[0]
v = isort.code(_code)
results.append({"name": k, "code": v, "key": k})
results.append(
{
"name": "db.py",
"code": SQLMODEL_DB.format(uri=obj.conf.get_db_uri()),
"key": "db.py",
}
)
_instance = SQLModelConversion
else:
data = TortoiseConversion(table, obj.get_table_columns(table)).gencode()
for k, v in data.items():
_code = FormatCode(v, style_config="pep8")[0]
v = isort.code(_code)
results.append({"name": k, "code": v, "key": k})
_instance = TortoiseConversion

data = _instance(
table, obj.get_table_columns(table), obj.conf.get_db_uri()
).gencode()

for k, v in data.items():
_code = FormatCode(v, style_config="pep8")[0]
v = isort.code(_code)
results.append({"name": k, "code": v, "key": k})
return {"code": 20000, "msg": "ok", "data": results}


Expand Down
152 changes: 116 additions & 36 deletions dfs_generate/tpl.py → dfs_generate/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,80 @@ def query_all_by_limit(session: Session, page_number: int, page_size: int, **kwa
return session.exec(stmt).all()
"""

SQLMODEL_ROUTER = """
from fastapi import APIRouter, Depends
from sqlmodel import Session
import schema
import dao
from db import engine
$router_name = APIRouter(prefix="/$table", tags=["$table"])
@$router_name.get("/{id}", summary="通过ID查询详情")
def query_${router_name}_by_id(id: int) -> schema.Result[schema.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.query_by_id(session, id))
@$router_name.get("", summary="分页条件查询")
def query_${router_name}_all_by_limit(query: schema.$table = Depends(), page_number: int = 1, page_size: int = 10) -> schema.PageResult[schema.$table]:
with Session(engine) as session:
total = dao.count(session, **query.model_dump(exclude_none=True))
data = dao.query_all_by_limit(session, **query.model_dump(exclude_none=True), page_number=page_number, page_size=page_size)
return schema.PageResult.ok(data=data, total=total)
@$router_name.post("", summary="新增数据")
def create_${router_name}(instance: schema.$table) -> schema.Result[schema.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.create(session, instance))
@$router_name.patch("/{id}", summary="更新数据")
def update_${router_name}_by_id(id: int, instance: schema.$table) -> schema.Result[schema.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.update(session, id, instance))
@$router_name.delete("/{id}", summary="删除数据")
def delete_${router_name}_by_id(id: int) -> schema.Result[schema.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.delete_by_id(session, id))
"""

SQLMODEL_DB = """
from sqlmodel import create_engine
db_uri = "{uri}"
engine = create_engine(db_uri)
"""

SQLMODEL_MAIN = """
from fastapi import FastAPI
from router import {router_name}
app = FastAPI(title="DFS - FastAPI SQLModel CRUD",
description='''
[![](https://img.shields.io/github/stars/zy7y/dfs-generate)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/forks/zy7y/dfs-generate)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/repo-size/zy7y/dfs-generate?style=social)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/license/zy7y/dfs-generate)](https://gitee.com/zy7y/dfs-generate/blob/master/LICENSE)
支持ORM:[SQLModel](https://sqlmodel.tiangolo.com/)、[Tortoise ORM](https://tortoise.github.io/)
支持前端: [Vue](https://cn.vuejs.org/)、[React](https://zh-hans.react.dev/)'''
)
app.include_router({router_name})
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", reload=True, port=5000)
"""

TORTOISE_DAO = """
async def create(obj_in: schema.{table}) -> model.{table}:
Expand All @@ -81,80 +155,86 @@ async def update(id: int, obj_in: schema.{table}) -> Optional[model.{table}]:
await obj.save()
return obj
async def delete_by_id(session: AsyncSession, id: int) -> Optional[model.{table}]:
obj = await query_by_id(session, id)
async def delete_by_id(id: int) -> Optional[model.{table}]:
obj = await query_by_id(id)
if obj:
await obj.delete()
return obj
async def count(**kwargs) -> int:
return await model.{table}.count(**kwargs)
return await model.{table}.filter(**kwargs).count()
async def query_all_by_limit(page_number: int, page_size: int, **kwargs) -> List[model.{table}]:
offset = (page_number - 1) * page_size
limit = page_size
return await model.{table}.filter(**kwargs).(offset).limit(limit).all()
return await model.{table}.filter(**kwargs).offset(offset).limit(limit).all()
"""


SQLMODEL_ROUTER = """
TORTOISE_ROUTER = """
from fastapi import APIRouter, Depends
from sqlmodel import Session
import model
import schema
import dao
from db import engine
$router_name = APIRouter(prefix="/$table", tags=["$table"])
@$router_name.get("/{id}", summary="通过ID查询详情")
def query_${router_name}_by_id(id: int) -> schema.Result[model.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.query_by_id(session, id))
async def query_${router_name}_by_id(id: int) -> schema.Result[schema.$table]:
return schema.Result.ok(await dao.query_by_id(id))
@$router_name.get("", summary="分页条件查询")
def query_${router_name}_all_by_limit(query: schema.$table = Depends(), page_number: int = 1, page_size: int = 10) -> schema.PageResult[model.$table]:
with Session(engine) as session:
total = dao.count(session, **query.model_dump(exclude_none=True))
data = dao.query_all_by_limit(session, **query.model_dump(exclude_none=True), page_number=page_number, page_size=page_size)
return schema.PageResult.ok(data=data, total=total)
async def query_${router_name}_all_by_limit(query: schema.$table = Depends(), page_number: int = 1, page_size: int = 10) -> schema.PageResult[schema.$table]:
total = await dao.count(**query.model_dump(exclude_none=True))
data = await dao.query_all_by_limit(**query.model_dump(exclude_none=True), page_number=page_number, page_size=page_size)
return schema.PageResult.ok(data=data, total=total)
@$router_name.post("", summary="新增数据")
def create_${router_name}(instance: schema.$table) -> schema.Result[model.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.create(session, instance))
async def create_${router_name}(instance: schema.$table) -> schema.Result[schema.$table]:
return schema.Result.ok(await dao.create( instance))
@$router_name.patch("/{id}", summary="更新数据")
def update_${router_name}_by_id(id: int, instance: schema.$table) -> schema.Result[model.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.update(session, id, instance))
async def update_${router_name}_by_id(id: int, instance: schema.$table) -> schema.Result[schema.$table]:
return schema.Result.ok(await dao.update(id, instance))
@$router_name.delete("/{id}", summary="删除数据")
def delete_${router_name}_by_id(id: int) -> schema.Result[model.$table]:
with Session(engine) as session:
return schema.Result.ok(dao.delete_by_id(session, id))
async def delete_${router_name}_by_id(id: int) -> schema.Result[schema.$table]:
return schema.Result.ok(await dao.delete_by_id( id))
"""

SQLMODEL_DB = """
from sqlmodel import create_engine

db_uri = "{uri}"
TORTOISE_MAIN = """
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
engine = create_engine(db_uri)
"""
from router import $router_name
MAIN = """
from fastapi import FastAPI
from router import {router_name}
app = FastAPI(title="DFS - FastAPI Tortoise ORM CRUD",
description='''
[![](https://img.shields.io/github/stars/zy7y/dfs-generate)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/forks/zy7y/dfs-generate)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/repo-size/zy7y/dfs-generate?style=social)](https://github.com/zy7y/dfs-generate)
[![](https://img.shields.io/github/license/zy7y/dfs-generate)](https://gitee.com/zy7y/dfs-generate/blob/master/LICENSE)
app = FastAPI()
支持ORM:[SQLModel](https://sqlmodel.tiangolo.com/)、[Tortoise ORM](https://tortoise.github.io/)
支持前端: [Vue](https://cn.vuejs.org/)、[React](https://zh-hans.react.dev/)'''
app.include_router({router_name})
)
register_tortoise(
app,
db_url="$uri",
modules={"models": ["model"]},
generate_schemas=False,
add_exception_handlers=True,
)
app.include_router($router_name)
if __name__ == '__main__':
import uvicorn
Expand Down
Binary file added docs/sqlmodel/api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1556e3a

Please sign in to comment.