之前线上业务都是基于PEEWEE这个ORM的来做的,部分的复杂业务逻辑直接则是使用的原生SQL。基于最近的最近刚好想把线上一个后台系统整合改进,替换一个新的orm,也为了当做学习,所以切换当下比较流程的orm框架Sqlalchmey。
目前Sqlalchmey最新的版本已经是1.4.26版本了,而且也对异步协程已经早支持了!所以必须得学习一波了!
当然其实对于ORM使用的争议问题,我这里不细说,其实ORM使用与否主要看你个人的业务和使用需求和场景而定,有些人偏向原生SQL,感觉那带劲!有些人可能觉得orm直观,所以各有所爱吧!
ORM提供的是SQL工具包和对象关系的映射一种方式。
PS:engine = create_engine
PS:sessionmaker(bind=engine)
PS:db_session = sessionmaker(bind=engine)
PS:Base = declarative_base()
PS: session = db_session() # 创建会话
create_engine中的一些参数:
- pool_size=5, # 连接池大小
- pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
- pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
- echo= True 输出执行SQL日志
- max_overflow=10, 超出 pool_size 后可允许的最大连接数,默认为 10, 这 10 个连接在使用过后,不放在 pool 中,而是被真正关闭的
- pool_timeout=30, 获取连接的超时阈值,默认为 30 秒
- poolclass=NullPool 表示禁用连接池的方式
首先我这里使用的是最新版本的sqlalchmey,连接的postgresql数据看看,其实ORM已经屏蔽了底层连接处理,支持不同的数据库引擎。所以这个基本是保持一致的。只是引入的和需要安全的引擎不一样。
这里示例使用的还是同步的方式,协程的方式,后续在整合到Fastapi的时候,再展开!
示例参考来源:https://blog.csdn.net/u013992330/article/details/113655840
示例代码:
#!/usr/bin/evn python
# coding=utf-8
from sqlalchemy import Column, String, create_engine, Integer, Text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import time
import urllib
# - 1:引入数据库和会话引擎;
# 连接格式: 用户名:密码@localhost:端口/数据库名
password = 'xxxxxxxxxxxxx'
uri = f'postgresql+psycopg2://postgres:{urllib.parse.quote_plus(password)}@127.0.0.1:5432/ces'
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
db_session = sessionmaker(bind=engine)
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def createTable():
# 创建表
Base.metadata.create_all(engine)
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
session.close()
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '1' and User.name == 'hsh4').one()
print('name:', user.name)
session.close() # 关闭Session
def updateData():
# 更新操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
users = session.query(User).filter_by(name="xiaoxiao").first() # 查询条件
users.code = "1000010" # 更新操作
session.add(users) # 添加到会话
session.commit() # 提交即保存到数据库
session.close() # 关闭会话
def deleteData():
# 删除操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session() # 创建会话
delete_users = session.query(User).filter(User.id == "1").first()
if delete_users:
session.delete(delete_users)
session.commit()
session.close() # 关闭会话
def dropTable():
# 删除表
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session() # 创建会话
session.execute('drop table users')
session.commit()
session.close()
createTable()
insertData()
selectData()
updateData()
deleteData()
# dropTable()
最新一些相关的操作:
上面的示例中是在同一个线程的之中处理,完成,而且我们的每次的执行的相关的数据的提交的时候,都是创新新的会话对象来处理我们的数据库的连接的。
那如果我们的只是创建一个会话ID可以执行一连串的操作吗?
from sqlalchemy import Column, String, create_engine, Integer, Text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import time
import urllib
# - 1:引入数据库和会话引擎;
# 连接格式: 用户名:密码@localhost:端口/数据库名
password = 'xxxxxxxxx'
uri = f'postgresql+psycopg2://postgres:{urllib.parse.quote_plus(password)}@xxx.xxx.x.xx:5432/ces'
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
db_session = sessionmaker(bind=engine)
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
session = db_session() # 创建一个全局的会话
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def createTable():
# 创建表
Base.metadata.create_all(engine)
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
print("关闭了会话!!!")
print('当前会话ID:', id(session))
session.close()
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '2' and User.name == 'xiaoxao').one()
print('name:', user.name)
print('当前会话ID:', id(session))
session.close() # 关闭Session
def updateData():
# 更新操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
users = session.query(User).filter_by(name="xiaoxao32").first() # 查询条件
users.code = "1000010" # 更新操作
print('当前会话ID:',id(session))
session.add(users) # 添加到会话
session.commit() # 提交即保存到数据库
session.close() # 关闭会话
def deleteData():
# 删除操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session() # 创建会话
delete_users = session.query(User).filter(User.id == "1").first()
if delete_users:
session.delete(delete_users)
session.commit()
session.close() # 关闭会话
def dropTable():
# 删除表
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session() # 创建会话
session.execute('drop table users')
session.commit()
session.close()
createTable()
insertData()
selectData()
updateData()
# deleteData()
# dropTable()
上面的示例和前面的最大的不同是:
且我们的执行相关的操作的时候,不再新家一个会话ID去处理。
最终发现也可以执行相关的操作,好奇,为什么我们的意见对会话执行关闭了,它还可以继续执行操作的呢?
查看关于close的介绍说明:
意思是在1.4版本中是可以自己重新创建额的一个新的出来,那我就好奇,重新创建的新的话应该是新的ID,这个就不思其解了!有知道的话,希望能对小弟我指点一下。
上面的示例中【使用全局会话ID】是在同一个线程的之中处理完成。但是这种全局的会话ID在多线程情况下是否是线程安全的呢?
PS:关于session = db_session() # 创建会话
当执行会话创建的时候,其实这个不会马上从eengine连接池里面获取一个连接,而是当执行到我们的对数据具体的一些操作的时候才会执行,直到我们的对相关数据的操作整个整过过程也会一直持有,当数据操作完成后才关闭,才回去释放。
1:需要注意的一点是:会话需要执行close关闭,这个连接的才会重新被回收
测试验证一下在多线的环境环境的使用全局会话ID下问题:
from sqlalchemy import Column, String, create_engine, Integer, Text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import time
import urllib
from sqlalchemy.orm import scoped_session
# - 1:引入数据库和会话引擎;
# 连接格式: 用户名:密码@localhost:端口/数据库名
password = 'sssaaaaaaaxxxxxx'
uri = f'postgresql+psycopg2://postgres:{urllib.parse.quote_plus(password)}@xxxxxxxx:5432/ces'
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
db_session = sessionmaker(bind=engine)
# Session = scoped_session(db_session)
# 将连接交还给连接池
# session.remove(
# Session.remove()
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
session = db_session() # 创建会话
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def createTable():
# 创建表
Base.metadata.create_all(engine)
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
print("关闭了会话!!!")
print('当前会话ID:', id(session))
session.close()
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '2' and User.name == 'xiaoxao').one()
print('name:', user.name)
print('当前会话ID:', id(session))
session.close() # 关闭Session
def updateData():
# 更新操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
users = session.query(User).filter_by(name="xiaoxao32").first() # 查询条件
users.code = "1000010" # 更新操作
print('当前会话ID:',id(session))
session.add(users) # 添加到会话
session.commit() # 提交即保存到数据库
session.close() # 关闭会话
def deleteData():
# 删除操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session() # 创建会话
delete_users = session.query(User).filter(User.id == "1").first()
if delete_users:
session.delete(delete_users)
session.commit()
session.close() # 关闭会话
def dropTable():
# 删除表
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session() # 创建会话
session.execute('drop table users')
session.commit()
session.close()
# createTable()
def task1():
insertData()
def task2():
selectData()
from threading import Thread
for i in range(20):
t = Thread(target=task1)
t.start()
t = Thread(target=task2)
t.start()
# insertData()
# selectData()
# updateData()
# deleteData()
# dropTable()
只是上面的代码你会发现出现了异常:
说明了我们的全局的会话对象在多线程环境是不安全滴。
scoped_session内部其实是基于threding.local的方式来解决线程安全的问题。
而且当使用基于scoped_session的之后,我们的会话对象也还可以是一个全局的会话对象,只是返回的额是一个scoped_session,并且这个的会话的回收和挂你,不再是调用close,而只是接的使用remove的方法就可以归还回收连接的对象。
所以最终的方案的代码如下:
from sqlalchemy import Column, String, create_engine, Integer, Text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import time
import urllib
from sqlalchemy.orm import scoped_session
# - 1:引入数据库和会话引擎;
# 连接格式: 用户名:密码@localhost:端口/数据库名
password = 'xxxxxxxxxx'
uri = f'postgresql+psycopg2://postgres:{urllib.parse.quote_plus(password)}@4xxxxxxxxxx.x.x.x:5432/ces'
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
sessionmakerFac = sessionmaker(bind=engine)
# ==========================================
session = scoped_session(sessionmakerFac) # 创建会话
# 将连接交还给连接池
# session.remove()
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
print("关闭了会话!!!")
print('当前会话ID:', id(session))
# 将连接交还给连接池
session.remove()
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
# session = db_session()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '2' and User.name == 'xiaoxao').one()
print('name:', user.name)
print('当前会话ID:', id(session))
# session.close() # 关闭Session
# 将连接交还给连接池
session.remove()
def task1():
insertData()
def task2():
selectData()
from threading import Thread
for i in range(20):
t = Thread(target=task1)
t.start()
t = Thread(target=task2)
t.start()
在上面的示例中我们的是直接的使用了scoped_session返回的Session对象来操作数据了。为啥不继续使用scoped_session返回的Session对象再来创建一个新的Session呢?
session = scoped_session(sessionmakerFac)
session_01 = session()
session_02 = session()
print(id(session_01))
print(id(session_02))
从上面看的话:scoped_session其实是一个单例模式。初始化的对象只有一个。
省略部分的代码·····
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
db_session = sessionmaker(bind=engine)
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def createTable():
# 创建表
Base.metadata.create_all(engine)
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
print("关闭了会话!!!")
print('当前会话ID:', id(session))
session.close()
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
session = db_session()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '2' and User.name == 'xiaoxao').one()
print('name:', user.name)
print('当前会话ID:', id(session))
session.close() # 关闭Session
# createTable()
def task1():
insertData()
def task2():
selectData()
from threading import Thread
for i in range(20):
t = Thread(target=task1)
t.start()
t = Thread(target=task2)
t.start()
封装的主要目的是,为了能后续的操作数据的时候,相关的相关的事务的处理。
参考我以前的使用peewee的使用的习惯的话,我们会使用上下的的方式简单等着一个使用:
database = PooledPostgresqlExtDatabase(
db_config_const.get('db_name'),
max_connections=db_config_const.get('max_connections'),
stale_timeout=db_config_const.get('stale_timeout'), # 5 minutes.
timeout=db_config_const.get('timeout'),
**{'user': db_config_const.get('db_user'), 'host': db_config_const.get('db_host'),
'password': db_config_const.get('db_pass'), 'port': db_config_const.get('db_port')})
def GetSession():
return database
def GetSessionAtomic():
return database.atomic()
def session_scope_atomic():
return GetSessionAtomic()
# if session.is_closed():
# # session.connection_context()
# session.connect()
from contextlib import contextmanager
@contextmanager
def session_scope():
session = GetSession()
if session.is_closed():
# session.connection_context()
session.connect()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
def session_scope2():
session = GetSession()
with session.manual_commit():
session.begin() # Begin transaction explicitly.
try:
yield session
except:
session.rollback() # Rollback -- an error occurred.
raise
else:
try:
session.commit() # Attempt to commit changes.
except:
session.rollback() # Error committing, rollback.
raise
改到我们的sqlalchmey其实方式流程也是一样的:
这种的封装的方式,可以让我们的数据库的操作完全的独立于一些请求的上下文的处理, 特别是是使用fastapi的时候,我个人不喜欢使用依赖注入的方式来获取连接的会话的对象。所以我会使用下面的这种的方式.
完整的示例如下:
from sqlalchemy import Column, String, create_engine, Integer, Text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import time
import urllib
from sqlalchemy.orm import scoped_session
# - 1:引入数据库和会话引擎;
# 连接格式: 用户名:密码@localhost:端口/数据库名
password = 'xiao@#SsLIV(&%$cPha*m)+=asoEW-#'
uri = f'postgresql+psycopg2://postgres:{urllib.parse.quote_plus(password)}@47.99.189.42:5432/ces'
engine = create_engine(url=uri, echo_pool=True, echo=False, pool_size=10)
# - 2:使用会话引擎对象绑定会话工程创建类-sessionmaker(bind=engine):
# - 3:通过会话工程类创建具体的会话实例;
# expire_on_commit 是 现的一种方法是使用上下文管理器暂时expire_on_commit关闭
db_session = scoped_session(sessionmaker(bind=engine, expire_on_commit=True))
def GetSession():
return db_session()
from contextlib import contextmanager
@contextmanager
def session_scope():
session = GetSession()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
# - 4:引入ORM模型的基础的基类declarative,所有模型基类都是它;
Base = declarative_base()
# 定义User对象:
class User(Base):
# 表的名字:
__tablename__ = 'users'
# 表的结构:
id = Column(Integer, autoincrement=True, primary_key=True, unique=True, nullable=False)
name = Column(String(50), nullable=False)
sex = Column(String(4), nullable=False)
code = Column(String(50), nullable=False)
create_time = Column(String(50), nullable=False)
updater = Column(String(32))
update_time = Column(String(20), nullable=False, default=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
onupdate=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def createTable():
# 创建表
Base.metadata.create_all(engine)
def insertData():
# 插入操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
with session_scope() as session:
# 创建新User对象:
local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
new_user = User(name='xiaoxao32', sex='23', code='43535334', create_time=local_time, update_time=local_time)
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
def selectData():
# 查询操作
# - 5:使用的我们的会话实例对象进行具体的CURD操作。
with session_scope() as session:
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id == '2' and User.name == 'xiaoxao').one()
print('name:', user.name)
print('当前会话ID:', id(session))
# createTable()
def task1():
insertData()
def task2():
selectData()
#
from threading import Thread
for i in range(20):
t = Thread(target=task1)
t.start()
t = Thread(target=task2)
t.start()
总结:通过上面的这种方式,我们的后续在同步的一些框架的使用的话,直接的使用的这种上下扥操作的方式就可以对我们的数据库的进行的相关的的操作了!
不需要通过使用的吗依赖注入的方式来实现的获取会话对象了,且上面的这种方式是线程的安全的,在多线程的情况依然可以正常的使用。
session.query.filter(User.name == 'xiaozhong')
session.query.filter(User.age == 31)
session.query.filter(User.name != 'xiaozhong')
session.query.filter(User.age > 31)
#大小写敏感
session.query.filter(User.name.like('%xiao%'))
# 大小写不敏感
session.query.filter(User.name.ilike('%xiao%'))
表示查询在里面的
session.query.filter(~User.name.in_(['xiao', 'zhong', 'niihao']))
in子表达式:
~ 表示的是在不里面:
session.query.filter(~User.name.in_(session.query.filter(~User.name.in_(['xiao', 'zhong', 'niihao'])))
session.query.filter(User.name == None)
session.query.filter(User.name.is_(None))
session.query.filter(User.name != None)
session.query.filter(User.name.isnot(None))
from sqlalchemy import and_
方案1:
session.query.filter(and_(User.name == 'xsasda', User.age == 31))
方案2:
session.query.filter(User.name == 'xsasda', User.age == 31)
方案1:
session.query.query.filter(User.name == 'xsasda').filter(User.age == 31)
====
from sqlalchemy import or_
query.filter(or_(User.name == 'xsasda', User.name == 'xsasda2'))
query.join(Address, User.id==Address.user_id)
# 其他sql示例
result = session.query(User.username,func.count(Article.id)).join(Article,User.id==Article.uid).\
group_by(User.id).order_by(func.count(Article.id).desc()).all()
result = session.query(User.username,func.count(Article.id)).join(Article,User.id==Article.uid).\
group_by(User.id).order_by(func.count(Article.id).desc()).all()
更多的学习可以参考:https://www.cnblogs.com/zhongyehai/p/11828337.html
上面的示例中,其实已经算是我们的对会话的管理的一种封装和引入,如果我们的在fastapi中使用的是多线程的模式的话,那么我的可以直接的使用上面的那种的封装的方式来处理数据库的操作就可以了!
那这里我再介绍一下其他的处理的方式,这里主要是列举当前多数的人的使用的方式:
fastapi_sqlalchemy 具体地址为:https://hub.fastgit.org/mfreeborn/fastapi-sqlalchemy
3:这种就是我的上面那只使用上下文的方式来处理了(不过不建议使用在于协程路由内)
4:使用Alchemical,可以实现我的上述类似3 的处理方式
Alchemical具体地址为:https://hub.fastgit.org/miguelgrinberg/alchemical
主要流程思路是:
1:首先还是创建的全局的会话async_session
DATABASE_URL = f"postgresql+asyncpg://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_SERVER}:{settings.POSTGRES_PORT}/{settings.POSTGRES_DB}"
engine = create_async_engine(DATABASE_URL, future=True, echo=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
2:然后就是定义依赖注入方法
async def get_session() -> AsyncSession:
async with async_session() as session:
yield session
3:定义相关的数据表CRUD模型的操作,如get_user_by_username:
from db.models.user import User
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
async def create_user(
db_session: AsyncSession, username: str, hased_password: str, avatar: str
):
new_user = User(username=username, hashed_password=hased_password, avatar=avatar)
db_session.add(new_user)
await db_session.commit()
await db_session.flush()
return new_user
async def get_user_by_username(db_session: AsyncSession, username: str) -> User:
result = await db_session.execute(select(User).where(User.username == username))
return result.scalars().first()
4:然后在具体的路由进行依赖注入的实例化
@router.post("/login")
async def login_for_access_token(async_session=Depends(get_session)
):
user = await 如get_user_by_username(
async_session, form_data.username
)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "user_id": user.id}
案例来源:
https://hub.fastgit.org/grillazz/fastapi-sqlalchemy-asyncpg
1:首先还是创建的全局的会话async_session 和依赖方法
from typing import AsyncGenerator
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
global_settings = config.get_settings()
url = global_settings.asyncpg_url
engine = create_async_engine(
url,
future=True,
echo=True,
)
# expire_on_commit=False will prevent attributes from being expired after commit.
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
# Dependency
async def get_db() -> AsyncGenerator:
session = async_session()
try:
yield session
await session.commit()
except SQLAlchemyError as ex:
await session.rollback()
raise ex
finally:
await session.close()
2:定义模型的Base的基础
from typing import Any
from fastapi import HTTPException, status
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.declarative import as_declarative, declared_attr
@as_declarative()
class Base:
id: Any
__name__: str
# Generate __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
async def save(self, db_session: AsyncSession):
"""
:param db_session:
:return:
"""
try:
db_session.add(self)
return await db_session.commit()
except SQLAlchemyError as ex:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex))
async def delete(self, db_session: AsyncSession):
"""
:param db_session:
:return:
"""
try:
await db_session.delete(self)
await db_session.commit()
return True
except SQLAlchemyError as ex:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex))
async def update(self, db_session: AsyncSession, **kwargs):
"""
:param db_session:
:param kwargs:
:return:
"""
for k, v in kwargs.items():
setattr(self, k, v)
await self.save(db_session)
3:然后是具体的模型的实例的实现:
import uuid
from fastapi import HTTPException, status
from sqlalchemy import Column, String, select
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from the_app.models.base import Base
class Nonsense(Base):
__tablename__ = "nonsense"
id = Column(UUID(as_uuid=True), unique=True, default=uuid.uuid4, autoincrement=True)
name = Column(String, nullable=False, primary_key=True, unique=True)
description = Column(String, nullable=False)
def __init__(self, name: str, description: str):
self.name = name
self.description = description
@classmethod
async def find(cls, db_session: AsyncSession, name: str):
"""
:param db_session:
:param name:
:return:
"""
stmt = select(cls).where(cls.name == name)
result = await db_session.execute(stmt)
instance = result.scalars().first()
if instance is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={"Record not found": f"There is no record for requested name value : {name}"},
)
else:
return instance
4:对数据异步的操作
from fastapi import APIRouter, Depends, status
from sqlalchemy.ext.asyncio import AsyncSession
from the_app.database import get_db
from the_app.models.nonsense import Nonsense
from the_app.schemas.nnonsense import NonsenseResponse, NonsenseSchema
router = APIRouter(prefix="/v1/nonsense")
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=NonsenseResponse)
async def create_nonsense(payload: NonsenseSchema, db_session: AsyncSession = Depends(get_db)):
nonsense = Nonsense(**payload.dict())
await nonsense.save(db_session)
return nonsense
@router.get("/", response_model=NonsenseResponse)
async def find_nonsense(
name: str,
db_session: AsyncSession = Depends(get_db),
):
return await Nonsense.find(db_session, name)
@router.delete("/")
async def delete_nonsense(name: str, db_session: AsyncSession = Depends(get_db)):
nonsense = await Nonsense.find(db_session, name)
return await nonsense.delete(nonsense, db_session)
@router.patch("/", response_model=NonsenseResponse)
async def update_nonsense(
payload: NonsenseSchema,
name: str,
db_session: AsyncSession = Depends(get_db),
):
nonsense = await Nonsense.find(db_session, name)
await nonsense.update(db_session, **payload.dict())
return nonsense
1:使中间的方式的,它实现,感觉好像应该是存在问题,因为整个过程我看到是如果我们的数据库是查询耗时的,我它不是使用的协程的尝试处理的,这样估计可能会有问题,不过暂时还没来得及进行测试验证。
from contextvars import ContextVar
from typing import Dict, Optional, Union
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.engine.url import URL
from sqlalchemy.orm import Session, sessionmaker
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.types import ASGIApp
from fastapi_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
_Session: sessionmaker = None
_session: ContextVar[Optional[Session]] = ContextVar("_session", default=None)
class DBSessionMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app: ASGIApp,
db_url: Optional[Union[str, URL]] = None,
custom_engine: Optional[Engine] = None,
engine_args: Dict = None,
session_args: Dict = None,
commit_on_exit: bool = False,
):
super().__init__(app)
global _Session
engine_args = engine_args or {}
self.commit_on_exit = commit_on_exit
session_args = session_args or {}
if not custom_engine and not db_url:
raise ValueError("You need to pass a db_url or a custom_engine parameter.")
if not custom_engine:
engine = create_engine(db_url, **engine_args)
else:
engine = custom_engine
_Session = sessionmaker(bind=engine, **session_args)
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
with db(commit_on_exit=self.commit_on_exit):
response = await call_next(request)
return response
class DBSessionMeta(type):
# using this metaclass means that we can access db.session as a property at a class level,
# rather than db().session
@property
def session(self) -> Session:
"""Return an instance of Session local to the current async context."""
if _Session is None:
raise SessionNotInitialisedError
session = _session.get()
if session is None:
raise MissingSessionError
return session
class DBSession(metaclass=DBSessionMeta):
def __init__(self, session_args: Dict = None, commit_on_exit: bool = False):
self.token = None
self.session_args = session_args or {}
self.commit_on_exit = commit_on_exit
def __enter__(self):
if not isinstance(_Session, sessionmaker):
raise SessionNotInitialisedError
self.token = _session.set(_Session(**self.session_args))
return type(self)
def __exit__(self, exc_type, exc_value, traceback):
sess = _session.get()
if exc_type is not None:
sess.rollback()
if self.commit_on_exit:
sess.commit()
sess.close()
_session.reset(self.token)
db: DBSessionMeta = DBSession
这个上面的看得出,它是定义个全局的db: DBSessionMeta = DBSession,里面而已是使用 global _Session的方法,那加入我的多线程的情况使用的话,估计这个····
模型相关:
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, index=True)
credit_balance = Column(Integer)
username = Column(String)
email = Column(String)
password = Column(String)
time_created = Column(DateTime(timezone=True), server_default=func.now())
time_updated = Column(DateTime(timezone=True), onupdate=func.now())
fastapi_sqlalchemy 中的db使用方式:
import os
import sys
from datetime import timedelta
from dotenv import load_dotenv
from fastapi import File, UploadFile, Depends, FastAPI, HTTPException, status
from fastapi_sqlalchemy import DBSessionMiddleware, db
from logzero import logger
from models import User as ModelUser
load_dotenv('.env')
app = FastAPI()
app.add_middleware(DBSessionMiddleware, db_url=os.environ['DATABASE_URL'])
@app.post('/get_account_info')
async def login_for_account_info():
user = db.session.query(ModelUser).filter(ModelUser.id == user_id).first()
if not user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Incorrect username or password',
headers={'WWW-Authenticate': 'Bearer'},
)
account_info = {
'id': user.id,
'credit_balance': user.credit_balance,
'username': user.username,
'email': user.email,
'time_created': user.time_created,
'time_updated': user.time_updated
}
return {'account_info': account_info}
import asyncio
import sys
from fastapi import FastAPI
from sqlalchemy import Column, Integer, String
from alchemical.aio import Alchemical
app = FastAPI()
db = Alchemical('sqlite:///app.db')
class User(db.Model):
id = Column(Integer, primary_key=True)
name = Column(String(128))
@app.get('/')
async def index():
async with db.Session() as session:
users = await session.scalars(User.select())
return {'users': [u.name for u in users]}
async def add():
"""Add test user."""
async with db.begin() as session:
session.add(User(name='test'))
if sys.argv[1] == 'init':
asyncio.run(db.create_all())
elif sys.argv[1] == 'add':
asyncio.run(add())
else:
raise RuntimeError('Invalid command')
以上是我的认知的几种的总结。
END
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
公众号:微信搜【小儿来一壶枸杞酒泡茶】
小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822
1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。 2:关于文章内容,部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。
原文作者:程序员小钟同学;如有侵权,请联系管理员删除。