Pull to refresh

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

Reading time 5 min
Views 113K
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 в различных сценариях. Весьма любопытно.

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


Хорошая альтернатива Алхимии, как считаете?
Tags:
Hubs:
+46
Comments 46
Comments Comments 46

Articles