随着大环境的跨平台需求越来越多,对与开发环境和实际运行环境都有跨平台的需求,Python开发和部署上都是跨平台的,本篇随笔介绍基于SqlAlchemy+Pydantic+FastApi的Python开发框架的技术细节,以及一些技术总结。

最近这几个月一直忙于Python开发框架的整合处理,将之前开发框架中很多重要的特性加入进来,并且兼容我们基于.netcore 开发的《SqlSugar开发框架》的接口标准,因此对于《SqlSugar开发框架》中的Winform前端、Vue3+Typescript+ElementPlus前端、WPF前端等都可以实现无缝的接入,避免从零开始完成这些接入端的开发,迅速整合即可实现相关的管理功能。

1、开发工具及开发环境

Python开发使用通用的VSCode开发工具进行开发就可以,非常方便,而且通过使用“Fitten Code” 的AI辅助插件,编码效率非常高。之前在随笔《Python 开发环境的准备以及一些常用类库模块的安装》中有一些相关的模块介绍,有兴趣可以参考了解一下。

1)关于开发语言

Python开发语言非常容易理解,介于强类型语言C#、Java和弱类型语言Javascript 之间,更加类似TypeScript一些,对应一些类型的定义和处理,更是TypeScript的翻版一样。我们在学习的时候,综合性的了解他的数据类型、控制语句、变量和函数定义、以及类的处理方式之间的差异就可以了,但多数的理念和实现方式都是类似的,一些差异可能是语言的特性,如Python语言的灵活性导致的。

2)关于常用类库

Python开发的有很多方面常用类库,如字符串处理、文件处理、数据库处理,图形处理、音视频处理、科学计算、网络处理、人工智能等等领域,一些基本上是标准的解决方案类库,一开始我们可以泛泛的了解一下,大致有一个方向。随着我们对于具体解决方案的细化,我们逐步深入探讨各种类似类库的不同,如对于后端Web API的处理,可能有FastAPI 、Django 、Flask等,对于数据库的访问,有特定的类库如pymysql 、pymssql、psycopg2、pymongo、

aiosqlite等(还分同步和异步类库),也有通用的ORM类库处理,如SQLAlchemyDjango Models等等。
Python开发提供的开源类库很多,有时候会看花了眼,不过我们随着学习和了解的深入,一般都能够了解到它们之间的一些差异或者历史原因,我们会逐步知道使用那些最为适合的类库。
3)关于开发工具
VSCode开发可以说是一个很万能的免费开发工具了,和一些大公司做的Python开发工具可能都收费的不同,微软这个可以说是业界良心,而且提供很多好用的插件提高开发效率,而且我们很多时候做前端开发,如Vue3+TypeScript+ElementPlus前端我也是基于VScode工具进行开发,对于UniApp+H5的移动端项目,也可以使用VScode进行开发,开发起来都非常的高效。

对于开发环境的跨平台也是一种很好的体验,由于VScode也看安装在MacOS上,因此不管你使用的是Window开发环境,还是苹果的MacOS开发环境,都是一样的,可以说是丝滑无比,所有的开发习惯都是一样的。

有人担心Python的编译环境是解析型的处理方式,可能处理效率上会比编译型的语言效率差很多,我实际开发的时候,同一个项目的后端,对比我们的.netcore的SqlSugar 开发框架后端,Python的启动更快,处理上也没有明显的差异。

2、框架的特点

1)分层处理及基类抽象

框架的分层沿用一些通用的做法,如下所示:

分层介绍 Java、C#开发 Python开发框架
视图、控制器 Controller api
数据传输 DTO schema
业务逻辑 Service + Interface service
数据访问 DAO / Mapper crud
模型 Model / Entity model

即使按照分层逻辑的划分,我们对于每个分层中的对象,我们都应该尽量减少重复编码,因此使用Python的继承关系来抽象一些通用的属性或者接口及实现等,以便实现更加高效的开发,减少冗余代码。

如对于我在C#开发框架中,后端的WebAPI我们采用下面的继承方式来实现一些逻辑的剥离和抽象。

对于Python开发框架来说,我们也是可以采用这种继承的思路,不过实现的时候,有一些语言上的差异。

相对而言,由于Python的特性,我们在实现上更加扁平化一些,不过主要的逻辑CRUD等处理放在了BaseController控制器类中处理。

如对于路由器,我们通过泛型参数的处理,让基类的接口更加个性化一些,如下代码所示。

这样我们抽象的基类接口具有更多的个性化特性,对于子类来说,只需要传入对应的类型来构造即可生成不一样的接口实现。

通过基类控制器的定义,我们接受子类控制器传入的信息即可。

class BaseController(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]):

类似BaseController的基类定义,我们对应的BaseCrud也是针对数据库访问的常规处理,我们做了抽象的封装。

通过基类BaseCrud定义,我们接受一些子类对象的不同参数实现个性化实现。

class BaseCrud(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]):

对于不同的业务表,我们继承BaseCrud,并传递不同对象的参数,就可以具有非常强大、丰富的函数处理功能了。

其中我们在基类Crud类中实现了常规查询条件的处理逻辑,以及排序规则,可以默认排序,或者根据查询对象的排序条件进行排序处理。

class CRUDUser(BaseCrud[User, int, UserPageDto, UserDto]):
"""实现自定义接口""" def apply_default_sorting(self, query: Query[User]) -> Query[User]:
"""默认排序-修改为根据名称倒序"""
return super().apply_default_sorting(query).order_by(User.name.desc())

在具体化一个Crud子类定义的时候,我们可以传入对应定义的模型类、主键类型、分页查询对象、常规DTO交互对象等信息进行处理,如果我们需要改变排序规则,重写 apply_default_sorting 函数即可。

由于我们框架中对数据库的访问采用SqlAlchemy来实现,也就是基于ORM的方式实现各种异步操作,因此处理逻辑上都是通用的。我们也可以为业务类增加相关的具体处理函数接口,如下面截图所示。

其他业务的Crud类也是一样的处理方式,继承BaseCrud即可,只有在特殊的处理才需要增加自己的接口函数。

我们看到Web API的控制器和数据访问对象,都是基类的抽象方式实现主要的逻辑。

我们在模型类(和数据库交互)上处理,也是一样,也通过定义一个基类的方式来实现一些基础的定义,如id,表名、id主键默认值处理等。

一般定义一个类似下面的类,如Base类,作为所有数据库模型类的基类。

@as_declarative()
class Base:
id: Any __name__: str
__abstract__ = True # Generate __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()

对于一些字符型的id主键,我们有时候,希望它能够自动初始化一个类似Guid的字符串(Python这里是uuid),那么我们再扩展一下模型基类的定义为 BaseModel

class BaseModel(Base):
"""定义id字段, 提供默认的构造函数,生成 UUID 作为 ID的默认值""" __abstract__ = True @declared_attr
def id(cls):
# 假设默认使用 UUID,如果需要使用自增整型,在子类中直接定义 id
return Column(String(36), primary_key=True, default=lambda: str(uuid.uuid1())) def __init__(self, **kwargs):
super().__init__(**kwargs)
# 获取 id 字段的列对象
id_column = self.__class__.__table__.columns.get("id") # 根据 id 列对象的类型进行初始化
if id_column is not None:
if isinstance(id_column.type, String):
if self.id is None:
self.id = str(uuid.uuid1())
elif isinstance(id_column.type, Integer):
# 整数类型不需要特别处理,通常由数据库自增
pass

对于通用的模型类,我们定义如下。

对于字符型uuid类型值的模型类,我们定义如下所示。

这样它在写入数据库的时候,Id主键的默认空值会被有序的GUID值替换,不需要每次人工赋值id,否则忘记了就会提示无法写入记录。

通用对于DTO对象,作为UI界面上的交换对象,我们也做了基类的定义,默认BaseModel是pydantic 对象,pydantic 一般作为后端数据接入的处理类库,可以对数据格式进行校验和映射等处理。

我们可以在 SchemaBase 中进行了一些定制化的处理,这样可以让他满足我们实际的需要,另外通过 ConfigDict 的处理,我们让它和Model对象之间进行了属性映射处理,类似C#中的AutoMapper的处理吧。

综合前面的继承关系定义,如下所示界面。

最终项目的结构如下所示。

之前在随笔《使用FastAPI来开发项目,项目的目录结构如何规划的一些参考和基类封装的一些处理》中也对目录结构进行了一些介绍。

我们完成了项目后,运行FastAPI项目,如下所示。

启动项目后,可以看到WebAPI主页中有详细的Swagger文档介绍,非常方便参考使用。

每个业务模块中,由于继承了标准的基类,具有通用的接口,如果我们不需要,也可以在相应的EndPoint入口路由中移除。

2)多种数据库支持

虽然我们在开发某个系统的时候,一般用一种数据库即可,但是支持多数据库是能力,根据需要选择即可。我之前开发的多款框架中,都支持多种数据库的接入,如MySQL、SqlServer、Postgresql、SQLite、Oracle、MongoDB等,Python对这些数据库的支持都有对应的驱动类库来实现接入,我们SqlAlchemy的ORM能力,对它们进行整合,我们在配置的时候,指定不同的驱动连接字符串即可。

数据库的配置信息我们使用Pydantic 和 Pydantic-setting来实现 .env文件内容自动加载到Pydantic 对象中即可。如我们项目的.env环境配置文件如下。

然后我们引入 pydantic-settings,并通过定义一个Setting 的类,让它自动加载 .env 配置信息进来即可

class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=f"{BasePath}/.env", # 加载env文件
extra="ignore", # 加载env文件,如果没有在Settings中定义属性,也不抛出异常
env_file_encoding="utf-8",
env_prefix="",
case_sensitive=False,
)
# Env Database
DB_NAME: str
DB_USER: str
DB_PASSWORD: str
DB_HOST: str
DB_PORT: int

由于我们希望通过配置数据库类型来指定不同的连接字符串,因此需要对最终构建的异步访问的连接字符串 DB_URI_ASYNC 进行组装。

    # 转换为方法属性
@property
def DB_URI_ASYNC(self):
connect_string: str = ""
if self.DB_TYPE == "mysql":
self.DB_PORT = self.DB_PORT if self.DB_PORT > 0 else 3306
connect_string = f"mysql+aiomysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
elif self.DB_TYPE == "mssql":
self.DB_PORT = self.DB_PORT if self.DB_PORT > 0 else 1433
# 如果端口>0 并且不为1433,则加上端口号
portString = f":{self.DB_PORT}" if (self.DB_PORT != 1433) else ""
connect_string = f"mssql+aioodbc://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}{portString}/{self.DB_NAME}?driver=ODBC+Driver+17+for+SQL+Server" elif self.DB_TYPE == "sqlite":
# 文件放在sqlitedb目录下
connect_string = f"sqlite+aiosqlite:///app//sqlitedb//{self.DB_NAME}.db"
elif self.DB_TYPE == "postgresql":
self.DB_PORT = self.DB_PORT if self.DB_PORT > 0 else 5432
connect_string = f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
else:
return ""
return connect_string

最终,我们通过配置信息构建的连接字符串传入创建数据库访问对象的时候,代码如下。

# 异步处理
async_engine, async_session = create_engine_and_session(settings.DB_URI_ASYNC)

我们在类中定义一个get_db的异步连接对象作为数据库访问入口的依赖函数,如下所示。

async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""创建一个 SQLAlchemy 数据库会话-异步处理."""
async with async_session() as session:
yield session

这样我们在API控制器函数处理的时候,依赖这个get_db的异步连接对象函数即可。

    async def get(cls, id: int, db: AsyncSession = Depends(get_db)):
item = await user_crud.get(db, id)
........................

最终UserController中重写的get函数,也是调用user_crud里面的BaseCrud基类的函数,如下所示。

    async def get(self, db: AsyncSession, id: PrimaryKeyType) -> Optional[ModelType]:
"""根据主键获取一个对象"""
query = select(self.model).filter(self.model.id == id)
result = await db.execute(query)
item = result.scalars().first()
return item

这样我们除了再配置文件中定义不同的数据库类型和生成不同的连接字符串外,其他函数都没有具体化某个数据库类型,因此这种数据库的接入是无感的,可以以通用处理方式实现多种数据库的接入处理。

如在MySQL中,使用的是 mysql+aiomysql://

在SqlServer中,使用的是 mssql+aioodbc://

在Postgresql中,使用的是 postgresql+asyncpg://

等等

3)多种接入前端

前面介绍了我们新开发的PythonWeb API遵循《SqlSugar开发框架》中的Web API标准命名规则,也是采用Restful的命名规范处理,对于业务的接口我们采用统一的命名方式。因此前端部分我们不用从零开发,只是适当的进行一些处理即可重用已有的前端部分。

我们修改指定Winform 前端的配置的API路径,让它指向Python的Web API接口,即可对接Winform前端成功。

而对于Vue3+ElementPlus的BS前端界面,由于前端和后端是严格的分离模式,因此也是一样的方式处理即可。

其他前端部分也是类似的处理即可。

4)代码生成工具支持

在完成项目的成功整合后,我们对自己开发提出了更高的要求,虽然大多数规则已经进行了基类的抽象的处理,对于一个新增的业务表,我们还是需要在不同的分层目录中添加对应的子类,如控制器、CRUD数据访问类、Model模型类,DTO对象类等,特别是对于模型类和数据库表的一一对应代码,手工编写肯定比较枯燥,因此这些问题,我们使用代码生成工具一次性解决它。

我们在代码生成工具中加入Python的后端代码的生成,一键可以生成各层的类文件,其中包括最为繁琐的Model映射类信息。

基于SqlAlchemy+Pydantic+FastApi的Python开发框架的更多相关文章

  1. flask - fastapi (python 异步API 框架 可以自动生成swagger 文档) 常用示例 以及整合euraka nacos

    flask - fastapi    (python 异步API 框架  可以自动生成swagger 文档)  常用示例: 之前使用 flask 需要手动写文档, 这个可以自动生成, fastapi ...

  2. RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录

    RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录 RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用户和开发者最佳的.Net框架 ...

  3. RDIFramework.NET — 基于.NET的快速信息化系统开发框架 - 5.3 数据库连接管理模块

    RDIFramework.NET — 基于.NET的快速信息化系统开发框架 5.3 数据库连接管理模块 5.3 数据库连接管理模块 我们经常可以看到很多软件直接把数据库连接字符串放在软件执行目录下的配 ...

  4. 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构

    基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...

  5. 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程

    基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...

  6. 基于DDD的现代ASP.NET开发框架--ABP系列之1、ABP总体介绍

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  7. 基于webpack实现多html页面开发框架一 准备工作

    本系列主要介绍如何基于webpack实现多html页面开发框架,这里不讲webpack的基本概念,废话不多说,直奔主题! 前置条件: 1.安装node环境,自己去官网下载安装 2.新建文件夹webpa ...

  8. 《精通并发与Netty》学习笔记(07 - 基于Thrift实现Java与Python的RPC调用)

    上节我们介绍了基于Thrift实现java与java的RPC调用,本节我们基于Thrift实现Java与Python的RPC调用 首先,修改data.thirft文件,将命名空间由java改为py n ...

  9. 向大家介绍我的新书:《基于股票大数据分析的Python入门实战》

    我在公司里做了一段时间Python数据分析和机器学习的工作后,就尝试着写一本Python数据分析方面的书.正好去年有段时间股票题材比较火,就在清华出版社夏老师指导下构思了这本书.在这段特殊时期内,夏老 ...

  10. 基于股票大数据分析的Python入门实战(视频教学版)的精彩插图汇总

    在我写的这本书,<基于股票大数据分析的Python入门实战(视频教学版)>里,用能吸引人的股票案例,带领大家入门Python的语法,数据分析和机器学习. 京东链接是这个:https://i ...

随机推荐

  1. Java基础 韩顺平老师的 集合 的部分笔记

    498,集合介绍 499,集合体系图(两个图背下) package com.hspedu.collection; import java.util.ArrayList; import java.uti ...

  2. 顺序表_C

    // Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...

  3. electron安装成功记录

    1.登录官网查看当前最新版本对应的node,注意这里不要看php那个汉化的,他那个是老版本的,node对不上 2.nvm安装一个新的node 3.使用cnpm安装(npm安装还是报错了,记得删node ...

  4. 【MQTT】Mosquitto 入门案例

    参考博主StoneGeek的文章 https://www.cnblogs.com/sxkgeek/p/9140180.html 之前接触的是在应用程序之间的消息中间件技术 RabbitMQ, Kafk ...

  5. 【SpringCloud】Re03 Feign

    Feign是一个声明式的HttpClient?更简洁的实现Http请求发送 安装Feign组件: 配置Feign的依赖坐标: <?xml version="1.0" enco ...

  6. 【Java-GUI】11 Swing06 JTable

    静态数据表格的样子: package cn.dzz; import javax.swing.*; import java.awt.*; public class JTable { JFrame jFr ...

  7. Java项目生产启动、关闭脚本

    1.直接启动 #!/bin/bash #这里可替换为你自己的执行程序,其他代码无需更改 APP_NAME=XXXX-api-1.0.jar #使用说明,用来提示输入参数 usage() { echo ...

  8. Cython将Numpy数组转为自定义结构体

    技术背景 前面我们写过几篇关于Cython的文章,例如Cython计算谐振势.Cython与C语言的结合.Cython调用CUDA Kernel函数.Cython有着非常Pythonic的编程范式,又 ...

  9. 我们与高效工作流的距离:使用AI阅读工具ChatDOC+笔记软件Obsidian Slide,直接从 PDF 文献直接输出 PPT 报告

    我们与高效工作流的距离 在当今信息化的时代,为了实现高效工作和学习,如何实现快速地输入和输出成为每个人的必修课题. 然而,对于输入而言,每一天大量的信息,往往会使我们陷入信息过载和知识爆炸的困境,难以 ...

  10. .NET 8 中利用 MediatR 实现高效消息传递

    前言 MediatR 是 .NET 下的一个实现消息传递的库,轻量级.简洁高效,用于实现进程内的消息传递机制.它基于中介者设计模式,支持请求/响应.命令.查询.通知和事件等多种消息传递模式.通过泛型支 ...