Un paquete de marco-Agnóstico, fácil de integrar para SQLalchemy Orm.
Muy inspirado por Django Orm y elocuente Orm
Fácil integración para su proyecto existente como Fastapi:
from sqlalchemy_mixins import BaseMixin
class User ( Base , BaseMixin ):
pass Usa Pip
pip install SqlalchemyMixin
Aquí hay una demostración rápida de lo que pueden hacer nuestras mixins.
bob = User . create ( name = 'Bob' )
post1 = Post . create ( body = 'Post 1' , user = bob , rating = 3 )
post2 = Post . create ( body = 'long-long-long-long-long body' , rating = 2 ,
user = User . create ( name = 'Bill' ),
comments = [ Comment . create ( body = 'cool!' , user = bob )])
# filter using operators like 'in' and 'contains' and relations like 'user'
# will output this beauty: <Post #1 body:'Post1' user:'Bill'>
print ( Post . where ( rating__in = [ 2 , 3 , 4 ], user___name__like = '%Bi%' ). all ())
# joinedload post and user
print ( Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ())
# subqueryload posts and their comments
print ( User . with_subquery ( 'posts' , 'posts.comments' ). first ())
# sort by rating DESC, user name ASC
print ( Post . sort ( '-rating' , 'user___name' ). all ())
# created_at, updated_at timestamps added automatically
print ( "Created Bob at " , bob . created_at )
# serialize to dict, with relationships import sqlalchemy as sa
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy_mixins import BaseMixin
app = Flask ( __name__ )
app . config [ 'SQLALCHEMY_DATABASE_URI' ] = 'sqlite://'
db = SQLAlchemy ( app )
######### Models #########
class BaseModel ( db . Model , BaseMixin ):
__abstract__ = True
pass
class User ( BaseModel ):
name = sa . Column ( sa . String )
######## Initialize ########
BaseModel . set_session ( db . session )
######## Create test entity ########
db . create_all ()
user = User . create ( name = 'bob' )
print ( user ) Esta biblioteca se basa en la bandera autocommit de Sqlalchemy. Debe establecerse en verdadero al inicializar la sesión, es decir:
session = scoped_session ( sessionmaker ( bind = engine , autocommit = True ))
BaseModel . set_session ( session ) o con Flask-SQLAlchemy
db = SQLAlchemy ( app , session_options = { 'autocommit' : True })Las características principales son
proporcionado por ActiveRecordMixin
El patrón de mapeadores de datos de Sqlalchemy es genial, pero el patrón de registro activo es más fácil y más seco.
Bueno, ¡lo implementamos además del mapeador de datos! Todo lo que necesitamos es inyectar sesiones en la clase ORM mientras arrugas nuestra aplicación:
BaseModel . set_session ( session )
# now we have access to BaseOrmModel.session propertyTodos amamos Sqlalchemy, pero hacer crud es un poco complicado allí.
Por ejemplo, crear un objeto necesita 3 líneas de código:
bob = User ( name = 'Bobby' , age = 1 )
session . add ( bob )
session . flush ()Bueno, teniendo acceso a la sesión del modelo, podemos escribir
bob = User . create ( name = 'Bobby' , age = 1 )Así es como se hace en Django Orm y Peewee
Los métodos de actualización y eliminación también se proporcionan
bob . update ( name = 'Bob' , age = 21 )
bob . delete ()Y, como en Django y elocuente, podemos recuperar rápidamente el objeto por identificación
User . get ( 1 ) # instead of session.query(User).get(1)y fallar si dicha identificación no existe
User . get_or_abort ( 123987 ) # will raise sqlalchemy_mixins.ModelNotFoundErrorComo en Flask-Sqlalchemy, Peewee y Django Orm, puede consultar rápidamente alguna clase
User . query # instead of session.query(User)También podemos recuperar rápidamente primero o todos los objetos:
User . first () # instead of session.query(User).first()
User . all () # instead of session.query(User).all() proporcionado por EagerLoadMixin
Si usa la carga ansiosa de Sqlalchemy, es posible que no sea muy conveniente, especialmente cuando queremos, digamos, cargar al usuario, todas sus publicaciones y comentarios a cada su publicación en la misma consulta.
Bueno, ahora puedes establecer fácilmente las relaciones de ORM que quieres cargar ansiosos
User . with_ ({
'posts' : {
'comments' : {
'user' : JOINED
}
}
}). all ()O podemos escribir propiedades de clase en lugar de cadenas:
User . with_ ({
User . posts : {
Post . comments : {
Comment . user : JOINED
}
}
}). all ()A veces queremos cargar relaciones en una consulta separada, es decir, hacer subconscorte. Por ejemplo, cargamos publicaciones en una página como esta, y para cada publicación queremos tener el usuario y todos los comentarios (y los autores de comentarios).
Para acelerar la consulta, cargamos comentarios en una consulta separada, pero, en esta consulta separada, únete al usuario
from sqlalchemy_mixins import JOINED , SUBQUERY
Post . with_ ({
'user' : JOINED , # joinedload user
'comments' : ( SUBQUERY , { # load comments in separate query
'user' : JOINED # but, in this separate query, join user
})
}). all ()Aquí, las publicaciones se cargarán en la primera consulta y los comentarios con los usuarios, en el segundo. Vea los documentos de Sqlalchemy para explicar las técnicas de carga de relaciones.
Para casos simples, cuando solo desea unirlo o subconmario, tenemos una sintaxis más fácil para usted:
Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ()
User . with_subquery ( 'posts' , 'posts.comments' ). all ()Tenga en cuenta que puede dividir las relaciones con Dot como
post.commentsdebido a esta función de Sqlalchemy
proporcionado por SmartQueryMixin
Implementamos búsqueda de campo tipo Django y uniones de relaciones automáticas.
¡Significa que puede filtrar y ordenar dinámicamente por atributos definidos en cadenas!
Entonces, al haber definido el modelo Post con la relación Post.user con el modelo User , puede escribir
Post . where ( rating__gt = 2 , user___name__like = '%Bi%' ). all () # post rating > 2 and post user name like ...
Post . sort ( '-rating' , 'user___name' ). all () # sort by rating DESC, user name ASC ( ___ divide la relación y el atributo, __ divide el atributo y el operador)
Si necesita más flexibilidad, puede usar el método
filter_exprsession.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text')), ver ejemplo.Es como
filter_byen Sqlalchemy, pero también permite a los operadores mágicos comorating__gt.Nota: El método
filter_expres de muy bajo nivel y no realiza uniones mágicas similares a Django. Usesmart_querypara eso.
Todas las relaciones utilizadas en el filtrado/clasificación deben establecerse explícitamente , no solo ser un backref
En nuestro ejemplo, la relación
Post.userdebe definirse en la clasePostincluso siUser.poststambién está definido.Entonces, no puedes escribir
class User ( BaseModel ): # ... user = sa . orm . relationship ( 'User' , backref = 'posts' )y omitir la definición de la relación
Post.user. Debes definirlo de todos modos:class Post ( BaseModel ): # ... user = sa . orm . relationship ( 'User' ) # define it anyway
Para la seca de su código e incapsulando la lógica comercial, puede usar los atributos híbridos de Sqlalchemy y los híbridos. Usarlos en nuestro filtrado/clasificación es sencillo (ver ejemplos y pruebas).
Bueno, como SmartQueryMixin hace auto-uniones para filtrar/clasificar, hay la sensación de decirle a Sqlalchemy que ya nos unimos a esa relación.
De modo que las relaciones se configuran automáticamente para unirse si se usaban para filtrar/clasificar.
Entonces, si escribimos
comments = Comment . where ( post___public = True , post___user___name__like = 'Bi%' ). all ()entonces no se ejecutará ninguna consulta adicional si accedemos a las relaciones usadas
comments [ 0 ]. post
comments [ 0 ]. post . user proporcionado por SmartQueryMixin
En el mundo real, queremos filtrar, clasificar y también cargar algunas relaciones a la vez. Bueno, si usamos lo mismo, digamos, User.posts Posts Relation en el filtrado y la clasificación, no debe unirse dos veces .
Es por eso que combinamos filtro, clasificación y carga ansiosa en un método más inteligente:
Comment . smart_query (
filters = {
'post___public' : True ,
'user__isnull' : False
},
sort_attrs = [ 'user___name' , '-created_at' ],
schema = {
'post' : {
'user' : JOINED
}
}). all ()Como desarrolladores, necesitamos depurar cosas con conveniencia. Cuando jugamos en repl, podemos ver esto
>>> session.query(Post).all()
[<myapp.models.Post object at 0x04287A50>, <myapp.models.Post object at 0x04287A90>]
Bueno, usando nuestra mezcla, podemos tener una salida más legible con ID de publicación:
>>> session.query(Post).all()
[<Post #11>, <Post #12>]
Aún más, en el modelo Post , podemos definir qué más (excepto ID) queremos ver:
class User ( BaseModel ):
__repr_attrs__ = [ 'name' ]
# ...
class Post ( BaseModel ):
__repr_attrs__ = [ 'user' , 'body' ] # body is just column, user is relationship
# ...Ahora tenemos
>>> session.query(Post).all()
[<Post #11 user:<User #1 'Bill'> body:'post 11'>,
<Post #12 user:<User #2 'Bob'> body:'post 12'>]
Y puede personalizar la longitud de Max __repr__ :
class Post(BaseModel):
# ...
__repr_max_length__ = 25
# ...
>>> long_post
<Post #2 body:'Post 2 long-long body' user:<User #1 'Bob'>>
proporcionado por DateMixin
Puede ver las marcas de tiempo creadas y actualizadas.
bob = User ( name = "Bob" )
session . add ( bob )
session . flush ()
print ( "Created Bob: " , bob . created_at )
# Created Bob: 2019-03-04 03:53:53.606765
print ( "Pre-update Bob: " , bob . updated_at )
# Pre-update Bob: 2019-03-04 03:53:53.606769
time . sleep ( 2 )
bob . name = "Robert"
session . commit ()
print ( "Updated Bob: " , bob . updated_at )
# Updated Bob: 2019-03-04 03:53:58.613044