Peewee – лёгкая, гибкая и очень быстрая ORM на Python

в 9:35, , рубрики: flask, orm, python, базы данных, Веб-разработка, Питон, метки: , , , , ,

image

Предлагаю всем джангистам/алхимистам немного отвечься и почитать вольную интерпретацию вводного туториала и частично документации по Peewee – stand-alone ORM, обязательной к ознакомлению любому питонщику и, в особенности, фласкеру. Пишут о ней мало, а зря. С Peewee очень просто подружиться, особенно если вы уже знакомы с какой-нибудь ORM на ActiveRecord. Что более важно – с ней приятно дружить :) Ну, начнём.

Установка
С pip:

pip install peewee

Из репозитория:

git clone https://github.com/coleifer/peewee.git cd peewee python setup.py install

Тесты:

python setup.py test

Есть обвязка для flask:

pip install flask-peewee

Определение моделей или «попахивает джангой»

Весь нижеследующий код можно повторить один к одному в интерактивном интерпретаторе или отдельном скрипте.

from peewee import *

db = SqliteDatabase('people.db')

class Person(Model):
    name = CharField()
    birthday = DateField()
    is_relative = BooleanField()

    class Meta:
        database = db  # модель будет использовать базу данных 'people.db'

Типов полей много, на все случаи жизни. Peewee берёт на себя преобразование питоновских объектов в значения, подходящие для базы данных, и наоборот.

Инициализирующие аргументы

Каждое поле принимает следующие инициализирующие аргументы:

  • null=False – возможно ли хранение null-значений;
  • index=False – создавать ли индекс для данного столбца в базе;
  • unique=False – создавать ли уникальный индекс для данного столбца в базе. См. также главу о составных индексах;
  • verbose_name=None – строка для человекопонятного представления поля;
  • help_text=None – строка с вспомогательным текстом для поля;
  • db_column=None – строка, явно задающая название столбца в базе для данного поля, используется например при работе с legacy базой данных;
  • default=None – значение по-умолчанию для полей класса при инстанцировании;
  • choices=None – список или кортеж двухэлементных кортежей, где первый элемент – значение для базы, второй – отображаемое значение (аналогично джанге);
  • primary_key=False – использовать ли данное поле, как первичный ключ;
  • sequence=None – последовательность для наполнения поля (удостоверьтесь, что бекэнд поддерживает такую функциональность);

Метаданные

Для каждой таблицы можно прописать единые метаданные в class Meta:

Опция Описание Наследуется?
database база данных для модели да
db_table название таблицы, в которой будут храниться данные нет
indexes список полей для индексирования да
order_by список полей для сортировки по-умолчанию да
primary_key составной первичный ключ, экземпляр класса CompositeKey, пример да
table_alias алиас таблицы для использования в запросах нет

Попробуем задать отношения между моделями через внешний ключ. С peewee это просто:

class Pet(Model):
    owner = ForeignKeyField(Person, related_name='pets')
    name = CharField()
    animal_type = CharField()

    class Meta:
        database = db  # модель будет использовать базу данных 'people.db'

Модели описаны, осталось создать для них соответствующие таблицы в базе данных:

>>> Person.create_table()
>>> Pet.create_table()

Работа с данными

Для примера создадим нескольких человек и заведём им домашних животных:

>>> from datetime import date
>>> uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15), is_relative=True)
>>> uncle_bob.save()  # cохраним Боба в базе данных

Записи можно создавать и напрямую с помощью метода Model.create() без явного save():

>>> grandma = Person.create(name='Grandma', birthday=date(1935, 3, 1), is_relative=True)
>>> herb = Person.create(name='Herb', birthday=date(1950, 5, 5), is_relative=False)

Порадуем бабулю фамилией:

>>> grandma.name = 'Grandma L.'
>>> grandma.save()  # обновим запись grandma

Теперь сгенерируем немного живности. У бабули аллергия на кошек, а вот у Герба есть некоторые проблемы:

>>> bob_kitty = Pet.create(owner=uncle_bob, name='Kitty', animal_type='cat')
>>> herb_fido = Pet.create(owner=herb, name='Fido', animal_type='dog')
>>> herb_mittens = Pet.create(owner=herb, name='Mittens', animal_type='cat')
>>> herb_mittens_jr = Pet.create(owner=herb, name='Mittens Jr', animal_type='cat')

В какой-то момент Варежке надоело жить с Гербом и, воспользовавшись открытым окном, он гордо убежал в закат. Уважая его право на свободу личности, всё же удалим соответствующую запись из базы:

>>> herb_mittens.delete_instance()  # удачи, Варежка
1

Как вы могли заметить, операция удаления возвращает количество удалённых записей, в данном случае – 1.

Дядя Боб решил, что у Герба итак много животных и отжал у него Фидо:

>>> herb_fido.owner = uncle_bob
>>> herb_fido.save()
>>> bob_fido = herb_fido  # переименуем переменную для лучшего соответствия суровой реальности

Выборки

Выборки выполняются прямо с объектом класса и возвращают экземпляры SelectQuery (аналог QuerySet в джанге).

Извлечение одной записи

Для извлечения одной записи используйте метод SelectQuery.get():

>>> grandma = Person.select().where(Person.name == 'Grandma L.').get()

Запрос можно сократить, подставив аргумент напрямую в get():

>>> grandma = Person.get(Person.name == 'Grandma L.')

Извлечение нескольких записей

Пройдемся по всем экземплярам Person циклом:

>>> for person in Person.select():
...     print person.name, person.is_relative
...
Bob True
Grandma L. True
Herb False

Пройдемся по экземплярам Person и по всем связанным с ними записями:

>>> for person in Person.select():
...     print person.name, person.pets.count(), 'pets'
...     for pet in person.pets:
...         print '    ', pet.name, pet.animal_type
...
Bob 2 pets
    Kitty cat
    Fido dog
Grandma L. 0 pets
Herb 1 pets
    Mittens Jr cat

Выловим всех кошек и их хозяев людей (или наоборот?):

>>> for pet in Pet.select().where(Pet.animal_type == 'cat'):
...     print pet.name, pet.owner.name
...
Kitty Bob
Mittens Jr Herb

Не без join'ов:

# выберем всех животных Боба
>>> for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
...     print pet.name
...
Kitty
Fido

Извлечь ту же выборку можно и по-другому – явно передав объект с Бобом в запрос:

>>> for pet in Pet.select().where(Pet.owner == uncle_bob):
...     print pet.name

Упорядочим выборку в алфавитном порядке. Для этого воспользуемся методом SelectQuery.order_by():

>>> for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
...     print pet.name
...
Fido
Kitty

Упорядочим людей по возрасту:

>>> for person in Person.select().order_by(Person.birthday.desc()):
...     print person.name
...
Bob
Herb
Grandma L.

Давайте попробуем более сложный запрос. Выберем всех людей, родившихся

  • до 1940
  • после 1959
>>> d1940 = date(1940, 1, 1)
>>> d1960 = date(1960, 1, 1)
>>> for person in Person.select().where((Person.birthday < d1940) | (Person.birthday > d1960)):
...     print person.name
...
Bob
Grandma L.
Хинт

Запрос where((Person.birthday < d1940) | (Person.birthday > d1960)) можно написать и как where(Person.birthday < d1940 or Person.birthday > d1960), но лучше этого не делать, т.к. peewee не всегда правильно обрабатывает такую запись.

А теперь торобоан. Выберем тех, кто родился между 1940 и 1960:

>>> for person in Person.select().where((Person.birthday > d1940) & (Person.birthday < d1960)):
...     print person.name
...
Herb

And one last thing. Воспользуемся SQL-функцией и выберем всех людей, чьё имя начинается с «G» в любом регистре:

>>> for person in Person.select().where(fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'):
...     print person.name
...
Grandma L.

Для выборок также используйте методы:

  • SelectQuery.group_by()
  • SelectQuery.having()
  • SelectQuery.limit() и SelectQuery.offset()

Если вам понравился данный краткий туториал, обязательно посетите официальную документацию — там много интересного, включая рецепты с решениями распространённых задач и набор плагинов, расширяющих базовую функциональность.

Бонус

Автора в его блоге спросили о быстродействии ORM, на что тот ответил:

On my machine peewee has been faster than Django and SQA at most tasks, and about the same when iterating and returning Model instances.

На моём компе peewee обошла Django и SQLAlchemy на большинстве задач, и показала сравнимые результаты на итерациях и выборке инстансов.

После чего опубликовал результаты бенчмарка на гитхабе. Тестились обычные модели и связанные через ForeignKey в различных сценариях. Весьма любопытно.

Кому интересно, исходники:

Хорошая альтернатива Алхимии, как считаете?

Автор: rzhannoy

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js