Un paquet de framework-agnostique, facile à intégrer pour Sqlalchemy Orm.
Fortement inspiré par Django Orm et Eloquent Orm
Intégration facile à votre projet existant comme Fastapi:
from sqlalchemy_mixins import BaseMixin
class User ( Base , BaseMixin ):
pass Utiliser Pip
pip install SqlalchemyMixin
Voici une démo rapide de ce que nos mélanges peuvent faire.
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 ) Cette bibliothèque repose sur le drapeau autocommit de Sqlalchemy. Il doit être défini sur true lors de l'initialisation de la session, c'est-à-dire:
session = scoped_session ( sessionmaker ( bind = engine , autocommit = True ))
BaseModel . set_session ( session ) ou avec Flask-SQLAlchemy
db = SQLAlchemy ( app , session_options = { 'autocommit' : True })Les principales caractéristiques sont
fourni par ActiveRecordMixin
Le motif de mappeur de données de Sqlalchemy est cool, mais le modèle d'enregistrement actif est plus facile et plus sec.
Eh bien, nous l'avons implémenté en plus du mappeur de données! Tout ce dont nous avons besoin est d'injecter simplement la session dans la classe ORM tout en bootstrap notre application:
BaseModel . set_session ( session )
# now we have access to BaseOrmModel.session propertyNous aimons tous Sqlalchemy, mais faire Crud est un peu délicat là-bas.
Par exemple, la création d'un objet nécessite 3 lignes de code:
bob = User ( name = 'Bobby' , age = 1 )
session . add ( bob )
session . flush ()Eh bien, ayant accès à la session à partir du modèle, nous pouvons simplement écrire
bob = User . create ( name = 'Bobby' , age = 1 )C'est comme ça que ça se fait dans Django Orm et Peewee
Les méthodes de mise à jour et de suppression sont également fournies
bob . update ( name = 'Bob' , age = 21 )
bob . delete ()Et, comme dans Django et éloquent, nous pouvons rapidement récupérer l'objet par id
User . get ( 1 ) # instead of session.query(User).get(1)et échouer si un tel identifiant n'existe pas
User . get_or_abort ( 123987 ) # will raise sqlalchemy_mixins.ModelNotFoundErrorComme dans Flask-Sqlalchemy, Peewee et Django Orm, vous pouvez rapidement interroger une classe
User . query # instead of session.query(User)Nous pouvons également récupérer rapidement les premiers ou tous les objets:
User . first () # instead of session.query(User).first()
User . all () # instead of session.query(User).all() Fourni par EagerLoadMixin
Si vous utilisez le chargement impatient de Sqlalchemy, vous pouvez le trouver pas très pratique, surtout quand nous voulons, disons, un utilisateur de chargement, tous ses messages et commentaires à chaque publication dans la même requête.
Eh bien, maintenant vous pouvez facilement définir les relations ORM que vous voulez à une charge impatiente
User . with_ ({
'posts' : {
'comments' : {
'user' : JOINED
}
}
}). all ()Ou nous pouvons écrire des propriétés de classe au lieu des chaînes:
User . with_ ({
User . posts : {
Post . comments : {
Comment . user : JOINED
}
}
}). all ()Parfois, nous voulons charger des relations dans une requête séparée, c'est-à-dire faire une sous-charge. Par exemple, nous chargeons des publications à la page comme celle-ci, et pour chaque publication, nous voulons avoir l'utilisateur et tous les commentaires (et les auteurs de commentaires).
Pour accélérer la question, nous chargeons des commentaires dans une requête séparée, mais, dans cette requête séparée, rejoignez l'utilisateur
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 ()Ici, les publications seront chargées sur la première requête et les commentaires avec les utilisateurs - dans le deuxième. Voir Sqlalchemy Docs pour expliquer les techniques de chargement des relations.
Pour des cas simples, lorsque vous voulez simplement rejoindre ou sous-positionner quelques relations, nous avons une syntaxe plus facile pour vous:
Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ()
User . with_subquery ( 'posts' , 'posts.comments' ). all ()Notez que vous pouvez diviser les relations avec Dot comme
post.commentsen raison de cette fonctionnalité SQLALCHEMY
fourni par SmartQueryMixin
Nous mettons en œuvre des recherches de champ de type Django et des jointures de relations automatiques.
Cela signifie que vous pouvez filtrer et trier dynamiquement par des attributs définis dans les chaînes!
Ainsi, après avoir défini le modèle Post avec la relation Post.user au modèle User , vous pouvez écrire
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 ( ___ divise la relation et l'attribut, __ divise l'attribut et l'opérateur)
Si vous avez besoin de plus de flexibilité, vous pouvez utiliser
filter_exprMethodsession.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text'))C'est comme
filter_bydans Sqlalchemy, mais permet également aux opérateurs magiques commerating__gt.Remarque: la méthode
filter_exprest très bas et ne fait pas de jointures de type django magiques. Utilisezsmart_querypour cela.
Toutes les relations utilisées dans le filtrage / le tri doivent être explicitement définies , pas seulement être un backref
Dans notre exemple, la relation
Post.userdoit être définie dans la classePostmême siUser.postsest également défini.Donc, tu ne peux pas taper
class User ( BaseModel ): # ... user = sa . orm . relationship ( 'User' , backref = 'posts' )et sauter la définition de la relation
Post.user. Vous devez le définir de toute façon:class Post ( BaseModel ): # ... user = sa . orm . relationship ( 'User' ) # define it anyway
Pour l'aménagement à sec votre code et votre logique commerciale incapulée, vous pouvez utiliser les attributs hybrides de Sqlalchemy et Hybrid_Methods. Les utiliser dans notre filtrage / tri est simple (voir des exemples et des tests).
Eh bien, comme SmartQueryMixin fait automatiquement des joins pour le filtrage / le tri, il y a un sens de dire à Sqlalchemy que nous avons déjà rejoint cette relation.
De sorte que les relations sont automatiquement définies pour être jointes à la charge si elles étaient utilisées pour le filtrage / le tri.
Donc, si nous écrivons
comments = Comment . where ( post___public = True , post___user___name__like = 'Bi%' ). all ()alors aucune requête supplémentaire ne sera exécutée si nous accéderons aux relations utilisées
comments [ 0 ]. post
comments [ 0 ]. post . user fourni par SmartQueryMixin
Dans le monde réel, nous voulons filtrer, trier et également charger des relations à la fois. Eh bien, si nous utilisons la même relation User.posts .
C'est pourquoi nous avons combiné un filtre, une trie et une charge avide dans une méthode la plus intelligente:
Comment . smart_query (
filters = {
'post___public' : True ,
'user__isnull' : False
},
sort_attrs = [ 'user___name' , '-created_at' ],
schema = {
'post' : {
'user' : JOINED
}
}). all ()En tant que développeurs, nous devons déboguer les choses avec la commodité. Lorsque nous jouons dans REPT, nous pouvons voir cela
>>> session.query(Post).all()
[<myapp.models.Post object at 0x04287A50>, <myapp.models.Post object at 0x04287A90>]
Eh bien, en utilisant notre mixin, nous pouvons avoir une sortie plus lisible avec des ID de poste:
>>> session.query(Post).all()
[<Post #11>, <Post #12>]
Encore plus, dans le modèle Post , nous pouvons définir ce que nous voulons voir d'autre (sauf ID):
class User ( BaseModel ):
__repr_attrs__ = [ 'name' ]
# ...
class Post ( BaseModel ):
__repr_attrs__ = [ 'user' , 'body' ] # body is just column, user is relationship
# ...Maintenant nous avons
>>> session.query(Post).all()
[<Post #11 user:<User #1 'Bill'> body:'post 11'>,
<Post #12 user:<User #2 'Bob'> body:'post 12'>]
Et vous pouvez personnaliser la longueur max __repr__ :
class Post(BaseModel):
# ...
__repr_max_length__ = 25
# ...
>>> long_post
<Post #2 body:'Post 2 long-long body' user:<User #1 'Bob'>>
fourni par DateMixin
Vous pouvez afficher les horodatages créés et mis à jour.
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