Пакет фреймностического, простого, простого в интеграции для SQLALCHEMY ORM.
Сильно вдохновлен Джанго Ормом и красноречивым Ормом
Легкая интеграция в ваш существующий проект, такой как Fastapi:
from sqlalchemy_mixins import BaseMixin
class User ( Base , BaseMixin ):
pass Используйте PIP
pip install SqlalchemyMixin
Вот быстрая демонстрация того, что могут сделать наши микшины.
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. Он должен быть настроен на True при инициализации сеанса, т.е.
session = scoped_session ( sessionmaker ( bind = engine , autocommit = True ))
BaseModel . set_session ( session ) Или с Flask-SQLAlchemy
db = SQLAlchemy ( app , session_options = { 'autocommit' : True })Основные особенности
Предоставлено ActiveRecordMixin
Паттерн Mapper Data Sqlalchemy - это круто, но активный шаблон записей проще и сухой.
Что ж, мы реализовали его поверх Mapper Data! Все, что нам нужно, - это просто ввести сеанс в класс ORM при начальной загрузке нашего приложения:
BaseModel . set_session ( session )
# now we have access to BaseOrmModel.session propertyМы все любим SQLALCHEMY, но делать мульти это немного сложно.
Например, создание объекта требует 3 строки кода:
bob = User ( name = 'Bobby' , age = 1 )
session . add ( bob )
session . flush ()Ну, имея доступ к сеансу из модели, мы можем просто написать
bob = User . create ( name = 'Bobby' , age = 1 )Вот как это делается в Джанго Орме и Пиви
Обновление и удаление также предоставляются методы
bob . update ( name = 'Bob' , age = 21 )
bob . delete ()И, как и в Джанго и красноречивом, мы можем быстро извлечь объект по 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 для объяснения методов загрузки отношений.
Для простых случаев, когда вы хотите просто соединить или загрузить несколько отношений, у нас есть более простой синтаксис для вас:
Comment . with_joined ( 'user' , 'post' , 'post.comments' ). first ()
User . with_subquery ( 'posts' , 'posts.comments' ). all ()Обратите внимание, что вы можете разделить отношения с точками, такими как
post.commentsиз -за этой функции SQLalchemy
Предоставлено SmartQueryMixin
Мы внедряем поиск поля, подобных Джанго и объединения автоматических отношений.
Это означает, что вы можете отфильтровать и динамически отбрасывать атрибуты, определенные в струнах!
Итак, имея определенную модель 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_exprsession.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text')), см. Пример.Это как
filter_byв SQLALCHEMY, но также позволяет магическим операторам, какrating__gt.ПРИМЕЧАНИЕ. Метод
filter_exprочень низкий уровень и не делает волшебные соединения, подобные Django. Используйтеsmart_queryдля этого.
Все отношения , используемые в фильтрации/сортировке
В нашем примере отношения
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>]
Что ж, используя наш миксин, мы можем иметь более читаемый вывод с идентификаторами почты:
>>> session.query(Post).all()
[<Post #11>, <Post #12>]
Более того, в Post -модели мы можем определить, что еще (кроме идентификатора) мы хотим увидеть:
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