使用SQLAlchemy查询数据库的功能方法
项目描述
SQLConstruct 是使用 SQLAlchemy 库查询数据库的功能方法。它被编写来提高速度,同时不引入难以维护和冗长的代码。相反,代码变得更加简单,因此发生错误的机会更少。
它旨在解决的问题
只读 SELECT 查询中的ORM开销;
加载不必要的列时的网络流量;
代码复杂性;
N+1问题。
最终
您描述您从数据库中想要获取的内容
from sqlconstruct import Construct, if_
product_struct = Construct({
'name': Product.name,
'url': url_for_product.defn(Product),
'image_url': if_(
Image.id,
then_=url_for_image.defn(Image, 100, 100),
else_=None,
),
})
然后您就可以获取它。 SQLConstruct 知道您需要哪些列以及如何将它们转换为可用的格式
>>> product = (
... session.query(product_struct)
... .outerjoin(Product.image)
... .first()
... )
...
>>> product.name
'Foo product'
>>> product.url
'/p1-foo-product.html'
>>> product.image_url
'//images.example.st/123-100x100-foo.jpg'
完整故事
基本准备
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, Text, ForeignKey
from sqlalchemy.orm import Session, relationship, eagerload
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite://')
Base = declarative_base()
class Image(Base):
__tablename__ = 'image'
id = Column(Integer, primary_key=True)
name = Column(String)
class Product(Base):
__tablename__ = 'product'
id = Column(Integer, primary_key=True)
name = Column(String)
image_id = Column(Integer, ForeignKey(Image.id))
description = Column(Text)
image = relationship(Image)
Base.metadata.create_all(engine)
session = Session(engine)
session.add(Product(name='Foo product', image=Image(name='Foo.jpg')))
session.commit()
def slugify(name):
# very dumb implementation, just for an example
return name.lower().replace(' ', '-')
def url_for_product(product):
return '/p{id}-{name}.html'.format(
id=product.id,
name=slugify(product.name),
)
def url_for_image(image, width, height):
return '//images.example.st/{id}-{width}x{height}-{name}'.format(
id=image.id,
width=width,
height=height,
name=slugify(image.name),
)
常规方法
>>> product = (
... session.query(Product)
... .options(eagerload(Product.image))
... .first()
... )
...
>>> product.name
u'Foo product'
>>> url_for_product(product)
'/p1-foo-product.html'
>>> url_for_image(product.image, 100, 100) if product.image else None
'//images.example.st/1-100x100-foo.jpg'
缺点
description 列不是延迟加载的,它将在每次加载时被加载;
如果您将 description 列标记为延迟加载,这可能会在您的项目中其他地方引入N+1问题;
如果您忘记 eagerload Product.image,您也会遇到N+1问题;
您必须将模型实例作为参数在项目的每个地方传递,这往往会导致代码复杂性,因为您不知道它们将来会如何被使用;
模型实例的创建并不便宜,CPU时间随着列数的增加而增加,即使它们都是延迟加载的。
初始解决方案
from sqlconstruct import Construct, apply_, if_
def url_for_product(product_id, product_name):
return '/p{id}-{name}.html'.format(
id=product_id,
name=slugify(product_name),
)
def url_for_image(image_id, image_name, width, height):
return '//images.example.st/{id}-{width}x{height}-{name}'.format(
id=image_id,
width=width,
height=height,
name=slugify(image_name),
)
product_struct = Construct({
'name': Product.name,
'url': apply_(url_for_product, args=[Product.id, Product.name]),
'image_url': if_(
Image.id,
then_=apply_(url_for_image, args=[Image.id, Image.name, 100, 100]),
else_=None,
),
})
用法
>>> product = (
... session.query(product_struct)
... .outerjoin(Product.image)
... .first()
... )
...
>>> product.name
u'Foo product'
>>> product.url
'/p1-foo-product.html'
>>> product.image_url
'//images.example.st/1-100x100-foo.jpg'
优点
只加载所需内容,无额外网络流量,无需延迟/取消延迟列;
url_for_product 和 url_for_image 函数无法增加复杂性,因为它们被迫将所有需要的列作为参数定义;
你正在使用预计算的值(在这个例子中是url)。
缺点
函数代码难以重构和重用,因为每次都应该指定或传递所有参数;
对连接操作要小心,因为如果不显式指定,SQLAlchemy 将产生表的笛卡尔积(SELECT ... FROM product, image WHERE ...),这将返回错误的结果并损害你的性能。
为了解决第一个缺点,SQLConstruct 提供了 define 装饰器,它赋予你定义混合函数的能力,以便以不同的方式使用它们。
from sqlconstruct import define
@define
def url_for_product(product):
def body(product_id, product_name):
return '/p{id}-{name}.html'.format(
id=product_id,
name=slugify(product_name),
)
return body, [product.id, product.name]
@define
def url_for_image(image, width, height):
def body(image_id, image_name, width, height):
return '//images.example.st/{id}-{width}x{height}-{name}'.format(
id=image_id,
width=width,
height=height,
name=slugify(image_name),
)
return body, [image.id, image.name, width, height]
现在这些函数可以按以下方式使用
>>> product = session.query(Product).first()
>>> url_for_product(product) # objective style
'/p1-foo-product.html'
>>> url_for_product.defn(Product) # apply_ declaration
<sqlconstruct.apply_ at 0x000000000>
>>> url_for_product.func(product.id, product.name) # functional style
'/p1-foo-product.html'
修改后的最终 Construct 定义
product_struct = Construct({
'name': Product.name,
'url': url_for_product.defn(Product),
'image_url': if_(
Image.id,
then_=url_for_image.defn(Image, 100, 100),
else_=None,
),
})
安装
要安装 SQLConstruct,只需
$ pip install sqlconstruct
测试过的 Python 版本:2.7,3.4,3.8。
测试过的 SQLAlchemy 版本:1.0,1.1,1.2,1.3。
上述示例使用 SQLAlchemy >= 0.9,如果你使用的是旧版本,你将不得不在项目配置中做出以下更改。
from sqlconstruct import QueryMixin
from sqlalchemy.orm.query import Query as BaseQuery
class Query(QueryMixin, BaseQuery):
pass
session = Session(engine, query_cls=Query)
Flask-SQLAlchemy
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app, session_options={'query_cls': Query})
或
db = SQLAlchemy(session_options={'query_cls': Query})
db.init_app(app)
许可证
SQLConstruct 采用 BSD 许可证发布。有关更多详细信息,请参阅 LICENSE.txt。
项目详情
SQLConstruct-0.2.4.tar.gz 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 23abf4b3d6fa3a08445b84e529e8cee4679d4fdcee2e6e81ba0310f3d9fab963 |
|
MD5 | 1ab40e91a3bb15b6a6570b5eb4f09e29 |
|
BLAKE2b-256 | 986ec3741c145aca13d38f6c4d8610c88adbb64d38850602fd1a680caf0d07b0 |