แพ็คของเฟรมเวิร์กที่ไม่เชื่อเรื่องง่ายและง่ายต่อการรวมเข้าด้วยกันสำหรับ Sqlalchemy ORM
แรงบันดาลใจอย่างมากจาก Django Orm และ Orm ที่มีคารมคมคาย
การรวมเข้ากับโครงการที่มีอยู่ของคุณอย่างง่าย ๆ เช่น Fastapi:
from sqlalchemy_mixins import BaseMixin
class User ( Base , BaseMixin ):
pass ใช้ PIP
pip install SqlalchemyMixin
นี่คือการสาธิตอย่างรวดเร็วเกี่ยวกับสิ่งที่ 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 ) ห้องสมุดนี้ขึ้นอยู่กับธง autocommit ของ SqlalChemy จะต้องตั้งค่าเป็นจริงเมื่อเริ่มต้นเซสชันเช่น:
session = scoped_session ( sessionmaker ( bind = engine , autocommit = True ))
BaseModel . set_session ( session ) หรือด้วย Flask-SQLAlchemy
db = SQLAlchemy ( app , session_options = { 'autocommit' : True })คุณสมบัติหลักคือ
จัดทำโดย ActiveRecordMixin
รูปแบบการทำแผนที่ข้อมูลของ Sqlalchemy นั้นยอดเยี่ยม แต่รูปแบบการบันทึกที่ใช้งานได้ง่ายที่สุดและแห้งกว่า
เรานำไปใช้กับข้อมูล Mapper! สิ่งที่เราต้องการคือเพียงแค่ฉีดเซสชันลงในคลาส ORM ในขณะที่ bootstrapping แอปของเรา:
BaseModel . set_session ( session )
# now we have access to BaseOrmModel.session propertyเราทุกคนรัก sqlalchemy แต่การทำ crud นั้นค่อนข้างยุ่งยาก
ตัวอย่างเช่นการสร้างวัตถุต้องการรหัส 3 บรรทัด:
bob = User ( name = 'Bobby' , age = 1 )
session . add ( bob )
session . flush ()มีการเข้าถึงเซสชันจากรุ่นเราสามารถเขียนได้
bob = User . create ( name = 'Bobby' , age = 1 )นั่นเป็นวิธีที่ทำใน Django Orm และ Peewee
อัปเดตและลบวิธีการเช่นกัน
bob . update ( name = 'Bob' , age = 21 )
bob . delete ()และเช่นเดียวกับใน Django และพูดจาดีเราสามารถดึงวัตถุโดย id ได้อย่างรวดเร็ว
User . get ( 1 ) # instead of session.query(User).get(1)และล้มเหลวหากไม่มีรหัสดังกล่าว
User . get_or_abort ( 123987 ) # will raise sqlalchemy_mixins.ModelNotFoundErrorเช่นเดียวกับใน Flask-Sqlalchemy, Peewee และ Django Orm คุณสามารถสอบถามคลาสได้อย่างรวดเร็ว
User . query # instead of session.query(User)นอกจากนี้เราสามารถเรียกคืนวัตถุก่อนหรือทั้งหมดได้อย่างรวดเร็ว:
User . first () # instead of session.query(User).first()
User . all () # instead of session.query(User).all() จัดทำโดย EagerLoadMixin
หากคุณใช้การโหลดที่กระตือรือร้นของ Sqlalchemy คุณอาจพบว่ามันไม่สะดวกโดยเฉพาะอย่างยิ่งเมื่อเราต้องการพูดโหลดผู้ใช้โพสต์และความคิดเห็นทั้งหมดของเขาในทุกโพสต์ของเขาในแบบสอบถามเดียวกัน
ตอนนี้คุณสามารถตั้งค่าความสัมพันธ์ orm ที่คุณต้องการโหลดได้อย่างง่ายดาย
User . with_ ({
'posts' : {
'comments' : {
'user' : JOINED
}
}
}). all ()หรือเราสามารถเขียนคุณสมบัติคลาสแทนสตริง:
User . with_ ({
User . posts : {
Post . comments : {
Comment . user : JOINED
}
}
}). all ()บางครั้งเราต้องการโหลดความสัมพันธ์ในแบบสอบถามแยกต่างหากเช่นทำคำถามย่อย ตัวอย่างเช่นเราโหลดโพสต์ในหน้าเช่นนี้และสำหรับแต่ละโพสต์เราต้องการให้ผู้ใช้และความคิดเห็นทั้งหมด (และผู้เขียนความคิดเห็น)
เพื่อเร่งความเร็วแบบสอบถามเราโหลดความคิดเห็นในแบบสอบถามแยกต่างหาก แต่ในแบบสอบถามแยกต่างหากเข้าร่วมผู้ใช้
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 ()ที่นี่โพสต์จะถูกโหลดในแบบสอบถามแรกและความคิดเห็นกับผู้ใช้ - ในที่สอง ดูเอกสาร Sqlalchemy สำหรับการอธิบายเทคนิคการโหลดความสัมพันธ์
สำหรับกรณีง่าย ๆ เมื่อคุณต้องการเข้าร่วมหรือ subqueryload ความสัมพันธ์บางอย่างเรามีไวยากรณ์ง่ายขึ้นสำหรับคุณ:
Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ()
User . with_subquery ( 'posts' , 'posts.comments' ). all ()โปรดทราบว่าคุณสามารถแยกความสัมพันธ์กับ dot เช่น
post.commentsเนื่องจากคุณสมบัติ sqlalchemy นี้
จัดทำโดย SmartQueryMixin
เราใช้การค้นหาฟิลด์เหมือน Django และการเชื่อมความสัมพันธ์อัตโนมัติ
หมายความว่าคุณสามารถ กรองและเรียงลำดับแบบไดนามิกโดยคุณลักษณะที่กำหนดไว้ในสตริง!
ดังนั้นการมีโมเดล Post ที่กำหนดไว้พร้อม Post.user ความสัมพันธ์ของผู้ใช้กับโมเดล User คุณสามารถเขียนได้
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 ( ___ แยกความสัมพันธ์และแอตทริบิวต์ __ แยกแอตทริบิวต์และตัวดำเนินการ)
หากคุณต้องการความยืดหยุ่นมากขึ้นคุณสามารถใช้เมธอด
filter_exprระดับต่ำsession.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text'))ดูตัวอย่างมันเหมือนกับ
filter_byใน sqlalchemy แต่ยังอนุญาตให้ผู้ประกอบการเวทมนตร์เช่นrating__gtหมายเหตุ: วิธี
filter_exprมีระดับต่ำมากและไม่ได้เข้าร่วมกับเวทมนตร์ Django ใช้smart_queryสำหรับสิ่งนั้น
ความสัมพันธ์ทั้งหมดที่ใช้ในการกรอง/การเรียงลำดับควรถูก ตั้งค่าอย่างชัดเจน ไม่ใช่แค่เป็น backref
ในตัวอย่างของเราควรกำหนดความสัมพันธ์ของ
Post.userในคลาสPostแม้ว่าUser.postsจะถูกกำหนดไว้ด้วยดังนั้นคุณไม่สามารถพิมพ์ได้
class User ( BaseModel ): # ... user = sa . orm . relationship ( 'User' , backref = 'posts' )และข้ามการกำหนดความสัมพันธ์
Post.userคุณต้องกำหนดมันต่อไป:class Post ( BaseModel ): # ... user = sa . orm . relationship ( 'User' ) # define it anyway
สำหรับการแห้งแล้งรหัสของคุณและตรรกะทางธุรกิจที่ไม่สามารถใช้งานได้คุณสามารถใช้แอตทริบิวต์ไฮบริดของ SqlalChemy และ Hybrid_Methods การใช้พวกเขาในการกรอง/การเรียงลำดับของเรานั้นตรงไปตรงมา (ดูตัวอย่างและการทดสอบ)
ในขณะที่ SmartQueryMixin เข้าร่วมอัตโนมัติสำหรับการกรอง/การเรียงลำดับมีความรู้สึกที่จะบอก sqlalchemy ว่าเราเข้าร่วมความสัมพันธ์นั้นแล้ว
เพื่อให้ความสัมพันธ์ถูกตั้งค่าให้เข้าร่วมโดยอัตโนมัติหากใช้สำหรับการกรอง/การเรียงลำดับ
ดังนั้นถ้าเราเขียน
comments = Comment . where ( post___public = True , post___user___name__like = 'Bi%' ). all ()จากนั้นจะไม่มีการสืบค้นเพิ่มเติมหากเราจะเข้าถึงความสัมพันธ์ที่ใช้แล้ว
comments [ 0 ]. post
comments [ 0 ]. post . user จัดทำโดย SmartQueryMixin
ในโลกแห่งความเป็นจริงเราต้องการกรองเรียงลำดับและกระตือรือร้นที่จะโหลดความสัมพันธ์บางอย่างในครั้งเดียว ถ้าเราใช้แบบเดียวกันพูด User.posts ความสัมพันธ์ในการกรองและการเรียงลำดับมัน ไม่ควรเข้าร่วมสองครั้ง
นั่นเป็นเหตุผลที่เรารวมตัวกรองเรียงลำดับและกระตือรือร้นในการโหลดในวิธีที่ฉลาดที่สุด:
Comment . smart_query (
filters = {
'post___public' : True ,
'user__isnull' : False
},
sort_attrs = [ 'user___name' , '-created_at' ],
schema = {
'post' : {
'user' : JOINED
}
}). all ()ในฐานะนักพัฒนาเราต้องดีบักสิ่งต่าง ๆ อย่างสะดวกสบาย เมื่อเราเล่นใน Repl เราจะเห็นสิ่งนี้
>>> session.query(Post).all()
[<myapp.models.Post object at 0x04287A50>, <myapp.models.Post object at 0x04287A90>]
ด้วยการใช้ Mixin ของเราเราสามารถมีเอาต์พุตที่อ่านได้มากขึ้นด้วยรหัสโพสต์:
>>> session.query(Post).all()
[<Post #11>, <Post #12>]
ยิ่งไปกว่านั้นในโมเดล Post เราสามารถกำหนดสิ่งอื่น (ยกเว้น id) ที่เราต้องการดู:
class User ( BaseModel ):
__repr_attrs__ = [ 'name' ]
# ...
class Post ( BaseModel ):
__repr_attrs__ = [ 'user' , 'body' ] # body is just column, user is relationship
# ...ตอนนี้เรามี
>>> session.query(Post).all()
[<Post #11 user:<User #1 'Bill'> body:'post 11'>,
<Post #12 user:<User #2 'Bob'> body:'post 12'>]
และคุณสามารถปรับแต่งความยาวสูงสุด __repr__ :
class Post(BaseModel):
# ...
__repr_max_length__ = 25
# ...
>>> long_post
<Post #2 body:'Post 2 long-long body' user:<User #1 'Bob'>>
จัดทำโดย DateMixin
คุณสามารถดูการประทับเวลาที่สร้างขึ้นและอัปเดต
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