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