GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Python 3
  • Скачать Python
  • IDE
  • Скачать PyCharm
  • Django
  • Tkinter
  • Книги

This view

  • Original page
  • New address

GeezerVison is showing python-scripts.com — Peewee ORM манипуляция базами данных

Ни для кого не новость, что большинство сегодняшних приложений взаимодействуют с базами данных. Особенно с движками на основе RDBMS (движки DB с поддержкой SQL). Как и любой другой язык программирования, Pyhton также предоставляет как собственные библиотеки для взаимодействия с базами данных, так и от третьих лиц. Как правило, вам нужно прописать запросы SQL для CRUD операций. Это нормально, однако иногда получается мешанина:

  • Шеф решил перейти с MySQL в… MSSQL и у вас нет выбора, кроме как кивнуть и внести правки в свои запросы в соответствии с другим движком баз данных;
  • Вам нужно сделать несколько запросов, чтобы получить одну часть данных из другой таблицы;
  • Список можно продолжать долго, не так ли?

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Python Форум Помощи

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Чат
Канал

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

Подписаться

Чтобы разобраться с этим, в игру вступает ORM.

Введение в ORM

ORM – это акроним от Object Relational Mapping (Объектно-реляционное отображение). Но что именно оно делает?

Из википедии:

Объектно-реляционное отображение — это технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных». Существуют как проприетарные, так и свободные реализации этой технологии.
Звучит круто, да?

Что такое Peewee?

Peewee (http://docs.peewee-orm.com/en/latest/) – это небольшое ORM, которое в данный момент поддерживает postgresql, mysql и sqlite. Разумеется, это не единственное ORM для разработчиков Python. К примеру, Django предоставляет собственную ORM библиотеку, кроме этого, всегда есть SqlAlchemy. Хорошая сторона Peewee – это то, что он занимает мало места, его легко освоить, и вы можете приступить к работе с приложениями за несколько минут.

Достаточно слов, перейдем к кодам!

Установка Peewee

Как и многие другие библиотеки Python, вы можете установить Peewee при помощи pip:

Shell
1
pip install peewee

Настройка базы данных

Как я говорил, Peewee поддерживает ряд движков для работы с базами данных (полный список тут: http://docs.peewee-orm.com/en/latest/peewee/database.html), в данной статье я использую MySQL.

Python
1
2
3
4
5
6
7
8
9
10
11
from peewee import *
 
user = 'root'
password = 'root'
db_name = 'peewee_demo'
 
dbhandle = MySQLDatabase(
    db_name, user=user,
    password=password,
    host='localhost'
)

Объект MySQLDatabase создан.

Создание моделей в Peewee

Сейчас я перейду к созданию моделей. В этой статье я использую две таблицы или модели: «Категория» и «Продукт«. Категория может содержать несколько продуктов. Сохраняем как файл models.py

models.py
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from peewee import *
 
class BaseModel(Model):
    class Meta:
        database = dbhandle
 
 
class Category(BaseModel):
    id = PrimaryKeyField(null=False)
    name = CharField(max_length=100)
 
    created_at = DateTimeField(default=datetime.datetime.now())
    updated_at = DateTimeField(default=datetime.datetime.now())
 
    class Meta:
        db_table = "categories"
        order_by = ('created_at',)

Сначала я создал BaseModel. Это связанно с тем, что Peewee просит вас передать dbhandle в каждый класс Model. Чтобы избежать лишних движений, я просто создал базовый класс и расширил его. Ознакомиться со всеми типами столбцов в таблице можно тут: http://docs.peewee-orm.com/en/latest/peewee/models.html#field-types-table

Хорошо, наша BaseModel и Category созданы. Модель Category состоит из четырех полей:

  1. id, который является полем автоматического прироста;
  2. name содержит имя категории;
  3. updated_at и created_at – поля timestamp, которые определяют настоящее время по умолчанию.

В классе Meta я передаю название таблицы в собственность db_table. Это не обязательно, если название таблицы и модели одинаковые. Собственность order_by указывает, какой столбец должен использоваться для сортировки данных во время извлечения. Вы можете переписать его, передав вид по полю на ваше усмотрение.

Перед тем как мы двинемся дальше, я хочу создать еще один файл под названием operations.py, в котором я буду использовать эти модели.

Python
1
2
3
4
5
6
7
8
9
import peewee
from models import *
 
if __name__ == '__main__':
    try:
        dbhandle.connect()
        Category.create_table()
    except peewee.InternalError as px:
        print(str(px))

После импорта, я подключаюсь к базе данных. Ошибка peewee.OperationalError ссылается на все ошибки, связанные с Peewee. К примеру, если вы введете неправильные учетные данные, вы получите следующее:

Python
1
(1045, "Access denied for user 'root1'@'localhost' (using password: YES)")

Затем мы вызываем Category.create_table(), которые создает таблицу с указанной ранее собственностью. Если вы передаете safe=True в качестве параметра, то существующая таблица просто перепишется. Это может привести к проблемам в реальной ситуации.

Далее, модель Product:

Python
1
2
3
4
5
6
7
8
9
10
11
12
class Product(BaseModel):
    id = PrimaryKeyField(null=False)
    name = CharField(max_length=100)
    price = FloatField(default=None)
    category = ForeignKeyField(Category, related_name='fk_cat_prod', to_field='id', on_delete='cascade',
                               on_update='cascade')
    created_at = DateTimeField(default=datetime.datetime.now())
    updated_at = DateTimeField(default=datetime.datetime.now())
 
    class Meta:
        db_table = "products"
        order_by = ('created_at',)

Она аналогична модели Cateogory. Разница только в ForeignKeyField, который указывает, как именно Product должен быть связан с Category. Обновление основы должно выглядеть следующим образом:

Python
1
2
3
4
5
6
7
8
9
10
if __name__ == '__main__':
    try:
        dbhandle.connect()
        Category.create_table()
    except peewee.InternalError as px:
        print(str(px))
    try:
        Product.create_table()
    except peewee.InternalError as px:
        print(str(px))

После запуска указанного выше кода, создается таблица модели product, а также отношение с таблицей categories. Вот скриншот моего клиента sql:

Peewee ORM манипуляция базами данных

Вставка записи (INSERT)

Теперь мы можем перейти к добавлению данных сперва в category, а затем в таблицу products. Так как у нас есть рабочие модели, не так просто добавлять или обновлять записи.

Python
1
2
3
4
5
6
7
8
9
10
import peewee
from models import *
 
def add_category(name):
    row = Category(
        name=name.lower().strip(),
    )
    row.save()
 
add_category('Books')

Я добавил функцию под названием add_category() с параметром и именем внутри. Объект Category создан, как и поля таблицы, которые являются переданной собственностью данного объекта класса. В нашем случае, это поле name.

The row.save() сохраняет данные из объекта в базу данных.

Круто, не так ли? Больше не нужно прописывать уродливые INSERT-ы.

Теперь добавим product.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import peewee
from models import *
 
 
def add_product(name, price, category_name):
    cat_exist = True
    try:
        category = Category.select().where(Category.name == category_name.strip()).get()
    except DoesNotExist as de:
        cat_exist = False
 
    if cat_exist:
        row = Product(
            name=name.lower().strip(),
            price=price,
            category=category
        )
        row.save()

add_product берет name, price и category_id в качестве вводных данных. Сначала, я проверю, существует ли категория, если да – значит её объекты хранятся в базе. В ORM вы имеете дело с объектом, по этому вы передаете информацию о категории в качестве объекта, так как мы уже определили эту взаимосвязь ранее.

Далее, я буду создавать разделы в main.py:

Python
1
2
add_category('Books')
add_category('Electronic Appliances')

Теперь добавим продукты:

Python
1
2
3
# Добавление продуктов.
add_product('C++ Premier', 24.5, 'books')
add_product('Juicer', 224.25, 'Electronic Appliances')

Полный main.py можете видеть ниже:

main.py
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import peewee
from models import *
 
 
def add_category(name):
    row = Category(
        name=name.lower().strip(),
    )
    row.save()
 
 
def add_product(name, price, category_name):
    cat_exist = True
    try:
        category = Category.select().where(Category.name == category_name.strip()).get()
    except DoesNotExist as de:
        cat_exist = False
 
    if cat_exist:
        row = Product(
            name=name.lower().strip(),
            price=price,
            category=category
        )
        row.save()
 
if __name__ == '__main__':
    # Создаем разделы.
    add_category('Books')
    add_category('Electronic Appliances')
    
    # Добавляем продукты в разделы.
    add_product('C++ Premier', 24.5, 'books')
    add_product('Juicer', 224.25, 'Electronic Appliances')

Я передаю имя категории, как только её объект будет найден и передан объекту класса Product. Если вы хотите пойти по пути SQL, для начала вам нужно выполнить SELECT, чтобы получить существующий category_id, и затем назначить id добавляемому продукту.

Так как работать с ORM – значит иметь дело с объектами, мы храним объекты вместо скалярных значений. Кто-то может посчитать это слишком «инженерным» методом в нашем случае, но подумайте о случаях, когда вы понятия не имеете о том, какая база данных должна быть использована в будущем. Ваш код – это универсальный инструмент для работы с базами данных, так что если он работает с MySQL, или MSSQL, то он будет работать даже с MongoDb (гипотетически).

Выбор нескольких записей

Сначала выделяем категории:

Python
1
2
3
4
5
6
7
8
9
import peewee
from models import *
 
 
def find_all_categories():
    return Category.select()
 
def find_all_products():
    return Product.select()

Category.select() возвращает ВСЮ запись, которая будет отсортирована по столбцу created_at, что мы и указали в классе Meta.

Теперь выбираем все продукты:

Python
1
2
3
4
5
6
7
8
9
10
products = find_all_products()
product_data = []
for product in products:
    product_data.append({
        'title': product.name,
        'price': product.price,
        'category': product.category.name
    })
 
print(product_data)

Здесь я перебираю продукты и добавляю запись в список product_data. Обратите внимание на доступ к категории продукта. Больше нет SELECT для получения ID, с последующим поиском названия. Простой цикл делает все за нас. При запуске, информация о продукте будет отображаться следующим образом:

Python
1
2
3
4
[
    {'price': 24.5, 'title': 'c++ premier', 'category': 'books'},
    {'price': 224.25, 'title': 'juicer', 'category': 'electronic appliances'}
]

Выбор одной записи

Чтобы выбрать одну запись, вам нужно использовать метод get:

Python
1
2
def find_product(name):
    return Product.get(Product.name == name.lower().strip())

Теперь это называется так:

Python
1
2
p = find_product('c++ premier')
print(p.category.name)

Название товара передано функции find_product, если запись существует – она вернет экземпляр Product. Здесь я вывожу категорию, связанную с этим продуктом.

Обновление записей в Peewee

Обновление записей – это так же просто, как и их создание. Вы получаете экземпляр объекта, после чего обновляете его.

Python
1
2
3
4
5
6
7
import peewee
from models import *
 
def update_category(id, new_name):
    category = Category.get(Category.id == id)
    category.name = new_name
    category.save()

После этого, вызываем его как:

Python
1
update_category(2, 'Kindle Books')

Удаление записей в Peewee

Удаление записи не сильно отличается от обновления:

Python
1
2
3
4
5
6
import peewee
from models import *
 
def delete_category(name):
    category = Category.get(Category.name == name.lower().strip())
    category.delete_instance()

Вызываем данную функцию для удаления раздела:

Python
1
delete_category('Kindle Books')

Обратите внимание на то, что после удаления записи, вы также удаляете связанные продукты, так как они подключены к категории, и мы уже определили сценарий во время удаления раздела ON_DELETE, будут удаляться и товары из раздела.

Готовое приложение
Сохраняем данные криптовалют от биржи Binance

Допустим у нас уже есть MySQL база данных и существует таблица: coins. Нам нужно создать модель для этой существующей таблице и сохранить полученные json данные от Binance.

Структура базы данных:

MySQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE `coins` (
  `id` int(11) NOT NULL,
  `symbol` varchar(20) NOT NULL,
  `priceChange` float NOT NULL,
  `priceChangePercent` float NOT NULL,
  `weightedAvgPrice` float NOT NULL,
  `prevClosePrice` float NOT NULL,
  `lastPrice` float NOT NULL,
  `lastQty` float NOT NULL,
  `bidPrice` float NOT NULL,
  `askPrice` float NOT NULL,
  `openPrice` float NOT NULL,
  `highPrice` float NOT NULL,
  `lowPrice` float NOT NULL,
  `volume` float NOT NULL,
  `quoteVolume` float NOT NULL,
  `openTime` int(30) NOT NULL,
  `closeTime` int(30) NOT NULL,
  `firstId` int(10) NOT NULL,
  `lastId` int(10) NOT NULL,
  `count` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Данные от Binance мы получим по ссылке: https://api.binance.com/api/v1/ticker/24hr для HTTP запроса мы будет использовать библиотеку Requests на примере.

Что мы реализуем в нашем приложении:

  • Добавление новых монет;
  • Проверка если монета существует;
  • Обновления данных по цене если монета существует;
  • Сортируем монеты по объему за 24 часа;
  • Кол-во монет которые торгуются с BNB в паре (Binance Coin);
  • Выбираем случайные 5 монет

Исходный код

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import requests
from peewee import MySQLDatabase, Model
from peewee import IntegerField, FloatField, CharField, PrimaryKeyField, TimestampField, fn
from peewee import InternalError
 
db = MySQLDatabase(
    'peewee', user='root', password='mangos',
    host='localhost'
)
 
 
class Coins(Model):
    id = PrimaryKeyField(null=False)
    symbol = CharField(unique=True)
    priceChange = FloatField()
    priceChangePercent = FloatField()
    weightedAvgPrice = FloatField()
    prevClosePrice = FloatField()
    lastPrice = FloatField()
    lastQty = FloatField()
    bidPrice = FloatField()
    askPrice = FloatField()
    openPrice = FloatField()
    highPrice = FloatField()
    lowPrice = FloatField()
    volume = FloatField()
    quoteVolume = FloatField()
    openTime = TimestampField()
    closeTime = TimestampField()
    firstId = IntegerField()
    lastId = IntegerField()
    count = IntegerField()
 
    class Meta:
        db_table = 'coins'
        database = db
 
# Создаем таблицу если не существует.
try:
    db.connect()
    Coins.create_table()
except InternalError as px:
    print(str(px))
 
# Получаем список криптовалют от Binance.
data = requests.get('https://api.binance.com/api/v1/ticker/24hr').json()
 
for coin in data:
    # Проверяем если валюта существует.
    exists = Coins.select().where(Coins.symbol == coin['symbol'])
 
    if bool(exists):
        print('Обновляем цены для:', coin['symbol'])
        # Запись существует.
        # Обновляем цены криптовалют.
        Coins.update(
            lastPrice=coin['lastPrice'],
            lastQty=coin['lastQty'],
            bidPrice=coin['bidPrice'],
            askPrice=coin['askPrice'],
        ).where(Coins.symbol == coin['symbol']).execute()
    else:
        print('Добавляем новую запись:', coin['symbol'])
        # Создаем новую запись.
        Coins.create(
            symbol=coin['symbol'],
            priceChange=coin['priceChange'],
            priceChangePercent=coin['priceChangePercent'],
            weightedAvgPrice=coin['weightedAvgPrice'],
            prevClosePrice=coin['prevClosePrice'],
            lastPrice=coin['lastPrice'],
            lastQty=coin['lastQty'],
            bidPrice=coin['bidPrice'],
            askPrice=coin['askPrice'],
            openPrice=coin['openPrice'],
            highPrice=coin['highPrice'],
            lowPrice=coin['lowPrice'],
            volume=coin['volume'],
            quoteVolume=coin['quoteVolume'],
            openTime=int(coin['openTime'] / 1000),
            closeTime=int(coin['closeTime'] / 1000),
            firstId=coin['firstId'],
            lastId=coin['lastId'],
            count=coin['count'],
        )
 
# Сортируем монеты по объему за 24 часа.
sorted_coins = Coins.select().order_by(Coins.volume.desc())
 
print('Сортировка по объему за 24 часа')
for coin in sorted_coins:
    print(coin.symbol, coin.volume)
 
print('-' * 30)
 
# Кол-во монет которые торгуются с BNB в паре (Binance Coin).
bnb_count = Coins.select().where(Coins.symbol.endswith('BNB')).count()
print('Кол-во монет в паре с BNB:', bnb_count)
 
print('-' * 30)
 
# Выбираем случайные 5 монет.
random = Coins.select().order_by(fn.Rand()).limit(5)
 
print('Случайные 5 монет:')
for coin in random:
    print(coin.symbol)

В третьей строке вы можете видеть список типов столбцов из таблицы, такие как Integer, Float и Varchar (IntegerField, FloatField, CharField, PrimaryKeyField, TimestampField). Подробнее про тип полей можете узнать из документации: https://peewee.readthedocs.io/en/2.0.2/peewee/fields.html

Больше примеров которые не были использованы: http://docs.peewee-orm.com/en/latest/peewee/querying.html

Результат который я получил:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/usr/bin/python3.5 /home/database/peewee-test.py
 
Добавляем новую запись: ETHBTC
Добавляем новую запись: LTCBTC
....
Добавляем новую запись: KEYBTC
Добавляем новую запись: KEYETH
 
Сортировка по объему за 24 часа
NPXSBTC 1778630000.0
KEYBTC 782103000.0
....
BCCBNB 349.373
REPBNB 70.274
------------------------------
Кол-во монет в паре с BNB: 70
------------------------------
Случайные 5 монет:
THETABTC
POWRBNB
IOTABNB
AGIBTC
STEEMETH
 
Process finished with exit code 0

Peewee ORM манипуляция базами данных

Меняем MySQL на SQLite

Используя пример из кода выше, мы изменим базу данных из MySQL на SQLite не трогая код (кроме самого подключения). Нам нужно обновить немного наш файл. С самых первых строк было:

Python
1
2
3
4
5
6
7
8
9
import requests
from peewee import MySQLDatabase, Model
from peewee import IntegerField, FloatField, CharField, PrimaryKeyField, TimestampField, fn
from peewee import InternalError
 
db = MySQLDatabase(
    'peewee', user='root', password='mangos',
    host='localhost'
)

Теперь у нас:

Python
1
2
3
4
5
6
import requests
from peewee import SqliteDatabase, Model
from peewee import IntegerField, FloatField, CharField, PrimaryKeyField, TimestampField, fn
from peewee import InternalError
 
db = SqliteDatabase('binance-coins.db')

После запуска отредактированного кода мы получим ошибку:

Shell
1
peewee.OperationalError: no such function: Rand

Ошибка в примере «Выбираем случайные 5 монет», в коде мы используем fn.Rand() и он работает для MySQL, но для SQLite и PostgreSQL он работать не будет, нужно использовать fn.Random(). Подробнее: http://docs.peewee-orm.com/en/latest/peewee/querying.html#getting-random-records

После запуска у нас появился файл базы данных binance-coins.db

Peewee ORM манипуляция базами данных

Вывод

Отлично, вы освоили основы Peewee и то, как вы можете использовать эту маленькую и эффективную ORM в ваших следующих проектах, связанных с Pyhton. Если у вас есть дополнительные вопросы – вы можете ознакомиться с официальной документацией и найти в ней ответы.

Vasile Buldumac

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: [email protected]

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.