Eine Packung von Framework-Agnostic, einfach zu integrieren für Sqlalchemy Orm.
Stark inspiriert von Django Orm und eloquent orm
Einfache Integration in Ihr vorhandenes Projekt wie Fastapi:
from sqlalchemy_mixins import BaseMixin
class User ( Base , BaseMixin ):
pass PIP verwenden
pip install SqlalchemyMixin
Hier ist eine kurze Demo dessen, was unsere Mixins tun können.
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 ) Diese Bibliothek basiert auf SQLalchemy's autocommit Flag. Es muss auf True eingestellt werden, wenn die Sitzung initialisiert wird. IE:
session = scoped_session ( sessionmaker ( bind = engine , autocommit = True ))
BaseModel . set_session ( session ) oder mit Flask-SQLAlchemy
db = SQLAlchemy ( app , session_options = { 'autocommit' : True })Hauptmerkmale sind
bereitgestellt von ActiveRecordMixin
Das Data Mapper -Muster von Sqlalchemy ist cool, aber das aktive Datensatzmuster ist am einfachsten und trockener.
Nun, wir haben es auf Data Mapper implementiert! Alles, was wir brauchen, ist nur eine Sitzung in die ORM -Klasse zu injizieren, während wir unsere App starten:
BaseModel . set_session ( session )
# now we have access to BaseOrmModel.session propertyWir alle lieben Sqlalchemy, aber Crud ist dort etwas schwierig.
Das Erstellen eines Objekts erfordert beispielsweise 3 Codezeilen:
bob = User ( name = 'Bobby' , age = 1 )
session . add ( bob )
session . flush ()Wenn wir Zugriff auf Sitzung vom Modell haben, können wir einfach schreiben
bob = User . create ( name = 'Bobby' , age = 1 )So wird es in Django Orm und Peewee gemacht
Aktualisieren und löschen Methoden werden ebenfalls bereitgestellt
bob . update ( name = 'Bob' , age = 21 )
bob . delete ()Und wie in Django und eloquent können wir das Objekt schnell per ID abrufen
User . get ( 1 ) # instead of session.query(User).get(1)und scheitern, wenn eine solche ID nicht existiert
User . get_or_abort ( 123987 ) # will raise sqlalchemy_mixins.ModelNotFoundErrorWie in Flask-Sqlalchemy, Peewee und Django Orm können Sie schnell eine Klasse abfragen
User . query # instead of session.query(User)Außerdem können wir schnell zuerst oder alle Objekte abrufen:
User . first () # instead of session.query(User).first()
User . all () # instead of session.query(User).all() bereitgestellt von EagerLoadMixin
Wenn Sie Sqlalchemys eifriges Laden verwenden, finden Sie es möglicherweise nicht sehr bequem, insbesondere wenn wir beispielsweise den Benutzer laden möchten, alle seine Beiträge und Kommentare zu jedem seiner Beitrag in derselben Abfrage.
Nun, jetzt können Sie einfach festlegen, welche Orm -Beziehungen Sie bestreiten möchten
User . with_ ({
'posts' : {
'comments' : {
'user' : JOINED
}
}
}). all ()Oder wir können Klasseneigenschaften anstelle von Zeichenfolgen schreiben:
User . with_ ({
User . posts : {
Post . comments : {
Comment . user : JOINED
}
}
}). all ()Manchmal wollen wir Beziehungen in separate Abfragen laden, dh unterzuziehen. Zum Beispiel laden wir solche Beiträge wie diese auf Seite und für jeden Beitrag möchten wir Benutzer und alle Kommentare (und kommentieren Autoren).
Um die Abfrage zu beschleunigen, laden wir Kommentare in separate Abfragen, aber in dieser separaten Abfrage haben wir den Benutzer bei
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 ()Hier werden Beiträge auf die erste Abfrage und Kommentare mit den Benutzern im zweiten Platz geladen. Siehe SQLalchemy -Dokumente zur Erklärung von Beziehungsbeladetechniken.
Für einfache Fälle haben wir, wenn Sie nur ein paar Beziehungen unterziehen oder unterfragen möchten, eine einfachere Syntax für Sie:
Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ()
User . with_subquery ( 'posts' , 'posts.comments' ). all ()
post.comments
bereitgestellt von SmartQueryMixin
Wir implementieren djangoähnliche Feld-Lookups und automatische Beziehungen.
Es bedeutet, dass Sie dynamisch nach Attributen filtern und sortieren können, die in Saiten definiert sind!
Nachdem Sie Post mit Post.user -Beziehung zum User definiert haben, können Sie schreiben
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 ( ___ spaltet Beziehung und Attribut, __ Spaltattribut und Operator)
Wenn Sie mehr Flexibilität benötigen, können Sie
filter_expr-Methodensitzungen mit niedrigem Niveausession.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text'))verwenden, siehe Beispiel.Es ist wie
filter_byin SQLalchemy, ermöglicht aber auch magische Operatoren wierating__gt.HINWEIS:
filter_expr-Methode ist sehr niedrig und macht keine magischen Django-ähnlichen Verbindungen. Verwenden Sie dafürsmart_query.
Alle zur Filterung/Sortierung verwendeten Beziehungen sollten explizit eingestellt werden, nicht nur ein Rückgriff
In unserem Beispiel sollte die Beziehung
Post.userinPostdefiniert werden, auch wennUser.postsauch definiert ist.Sie können also nicht tippen
class User ( BaseModel ): # ... user = sa . orm . relationship ( 'User' , backref = 'posts' )und überspringen Sie die Beziehung zwischen
Post.user. Sie müssen es trotzdem definieren:class Post ( BaseModel ): # ... user = sa . orm . relationship ( 'User' ) # define it anyway
Um Ihren Code zu trocken zu zeigen und die Geschäftslogik in der Inkapselung zu verwenden, können Sie die Hybridattribute von SQLalchemy und hybrid_methods verwenden. Die Verwendung in unserer Filterung/Sortierung ist unkompliziert (siehe Beispiele und Tests).
Nun, da SmartQueryMixin Auto-Join zum Filtern/Sortieren macht, ist es sinnvoll, Sqlalchemy zu sagen, dass wir uns dieser Beziehung bereits angeschlossen haben.
So dass die Beziehungen automatisch so eingestellt werden, dass sie zum Filtern/Sortieren verwendet wurden.
Also, wenn wir schreiben
comments = Comment . where ( post___public = True , post___user___name__like = 'Bi%' ). all ()Dann wird keine zusätzliche Abfrage ausgeführt, wenn wir auf gebrauchte Beziehungen zugreifen werden
comments [ 0 ]. post
comments [ 0 ]. post . user bereitgestellt von SmartQueryMixin
In der realen Welt wollen wir einige Beziehungen gleichzeitig filtern, sortieren und auch laden. Nun, wenn wir dasselbe verwenden, z . User.posts
Deshalb haben wir Filter, Sortier und eifrige Last in einer intelligenten Methode kombiniert:
Comment . smart_query (
filters = {
'post___public' : True ,
'user__isnull' : False
},
sort_attrs = [ 'user___name' , '-created_at' ],
schema = {
'post' : {
'user' : JOINED
}
}). all ()Als Entwickler müssen wir die Dinge mit Bequemlichkeit debuggen. Wenn wir in Repl spielen, können wir das sehen
>>> session.query(Post).all()
[<myapp.models.Post object at 0x04287A50>, <myapp.models.Post object at 0x04287A90>]
Mit unserem Mixin können wir mit Post -IDs mehr lesbare Ausgaben haben:
>>> session.query(Post).all()
[<Post #11>, <Post #12>]
Noch mehr können wir im Post definieren, was (außer ID) wir sehen möchten:
class User ( BaseModel ):
__repr_attrs__ = [ 'name' ]
# ...
class Post ( BaseModel ):
__repr_attrs__ = [ 'user' , 'body' ] # body is just column, user is relationship
# ...Jetzt haben wir
>>> session.query(Post).all()
[<Post #11 user:<User #1 'Bill'> body:'post 11'>,
<Post #12 user:<User #2 'Bob'> body:'post 12'>]
Und Sie können die Länge von Max __repr__ anpassen:
class Post(BaseModel):
# ...
__repr_max_length__ = 25
# ...
>>> long_post
<Post #2 body:'Post 2 long-long body' user:<User #1 'Bob'>>
bereitgestellt von DateMixin
Sie können die erstellten und aktualisierten Zeitstempel anzeigen.
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