In [ ]:
%%html
<style>
.text_cell_render * {
   font-family: OfficinaSansCTT;
}
.reveal code {
    font-family: OfficinaSansCTT;
}
.text_cell_render h3 {
   font-family: OfficinaSansCTT;
}
.reveal section img {
    max-height: 500px;
    margin-left: auto;
    margin-right: auto;
}
</style>

Вопросы

  • Какие есть два способа создать поток, используя модуль threading?
  • Что такое кооперативная многозадачность? Что такое Future?
  • В чем основные отличия между асинхронными и синхронными функциями в Python?
  • Какая функция при работе с корутинами является, грубо говоря, аналогом функции concurrent.futures.ThreadPoolExecutor().submit() из мира потоков?
  • Что такое MVC и какие файлы в Django отвечают за каждый компонент?
  • В чем главные отличия Django, Flask и aiohttp?

Асинхронный бот

Базы данных

  • Реляционные (MySQL, PostgreSQL, Oracle, SQLite)
  • Key-Value + document-oriented (Redis, Tarantool, MongoDB, Elasticsearch)
  • Графовые (Neo4j)
  • и т.д.
  • Распределенные? (DNS)
  • In-Memory? (Memcached)

Реляционные базы данных

  • Записи могут иметь ключи, указывающие друг на друга
  • Чаще всего для работы с данными используется SQL (https://ru.wikipedia.org/wiki/SQL)

SQL - Structured Query Language

SELECT emp.last_name AS Surname, d.title AS Department FROM departments d LEFT JOIN employees emp ON (d.id = emp.department_id)
CREATE TABLE IF NOT EXISTS `employees` (
  `id` int(6) unsigned NOT NULL,
  `first_name` varchar(30) NOT NULL,
  `last_name` varchar(30) NOT NULL,
  `department_id` int(6) unsigned,
  PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `departments` (
  `id` int(6) unsigned NOT NULL,
  `title` varchar(30) NOT NULL,
  PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `employees` (`id`, `first_name`, `last_name`, `department_id`) VALUES
  ('1', 'Darth', 'Vader', 1),
  ('2', 'Darth', 'Maul', 1),
  ('3', 'Kylo', 'Ren', 1),
  ('4', 'Magister', 'Yoda', 2),
  ('5', 'Leia', 'Organa', 2),
  ('6', 'Luke', 'Skywalker', 2),
  ('7', 'Jar Jar', 'Binks', NULL);

INSERT INTO `departments` (`id`, `title`) VALUES
  ('1', 'Dark Side Inc.'),
  ('2', 'Light Side Ltd.'),
  ('3', 'Rebels'),
  ('4', 'Wookie');

In [ ]:
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()

In [ ]:
c.execute("""
CREATE TABLE employees (
  id int unsigned NOT NULL,
  first_name string NOT NULL,
  last_name string NOT NULL,
  department_id int unsigned,
  PRIMARY KEY (id)
)""")
c.execute("""
CREATE TABLE departments (
  id int unsigned NOT NULL,
  title string NOT NULL,
  PRIMARY KEY (id)
)""")
conn.commit()

In [ ]:
c.execute("""
INSERT INTO `employees` (`id`, `first_name`, `last_name`, `department_id`) VALUES
  ('1', 'Darth', 'Vader', 1),
  ('2', 'Darth', 'Maul', 1),
  ('3', 'Kylo', 'Ren', 1),
  ('4', 'Magister', 'Yoda', 2),
  ('5', 'Leia', 'Organa', 2),
  ('6', 'Luke', 'Skywalker', 2),
  ('7', 'Jar Jar', 'Binks', NULL)
""")
c.execute("""
INSERT INTO `departments` (`id`, `title`) VALUES
  ('1', 'Dark Side Inc.'),
  ('2', 'Light Side Ltd.'),
  ('3', 'Rebels'),
  ('4', 'Wookie')
""")
conn.commit()

In [ ]:
c.execute("SELECT emp.last_name AS Surname, d.title AS Department FROM departments d LEFT JOIN employees emp ON (d.id = emp.department_id)")
print(c.fetchall())

ORM - Object-Relational Mapping

  • Установим соответствие между записями в базе и объектами в коде
  • Получим удобство в коде за счет меньшей гибкости построения запросов и большего оверхеда

Вернемся к нашему сайту

  • Миграции - это преобразования схемы и/или типов данных, меняющие структуру базы как в процессе разработки, так и на боевых серверах
  • python manage.py migrate

In [ ]:
# hello/models.py

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Нужно добавить наше приложение в INSTALLED_APPS в settings.py


In [ ]:
INSTALLED_APPS = [
    'hello',   # <---- вот сюда, например
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Создадим миграцию для наших новых моделей

  • python manage.py makemigrations hello
  • Но что конкретно он нагенерировал?
  • python manage.py sqlmigrate hello 0001
  • Ну вот, теперь все понятно
  • python manage.py migrate

Встроенный Python shell (рекомендую IPython)

  • python manage.py shell

In [ ]:
import django
django.setup()

from django.utils import timezone  # поддержка временных зон
from hello.models import Question, Choice

Question.objects.all()   # вернуть все объекты из базы
q = Question(question_text="Чёкак?", pub_date=timezone.now())  # создать объект
q.save()   # сохранить объект в базу
q.question_text = "Чёкаво?"
q.save()

In [ ]:
str(q.query)  # заглянуть внутрь
Question.objects.filter(question_text__startswith='Чё')  # фильтруем по строчкам
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year)   # фильтруем по году
Question.objects.get(id=2)
q.choice_set.all()  # все варианты ответа для данного вопроса
c = q.choice_set.create(choice_text='Кто бы знал', votes=0)  # создаем связанный объект
c.delete()   # удаляем объект

Админка

  • python manage.py createsuperuser

In [ ]:
# hello/admin.py

from django.contrib import admin

from .models import Question

admin.site.register(Question)
  • python manage.py runserver

А что насчет не-Django? SQLAlchemy, вот что!

Проблемы с реляционными базами

  • Не очень хорошо масштабируются
  • Любое изменение схемы приводит к гиганским миграциям
  • Плохо поддерживают асинхронность
  • Распространенные СУБД плохо интергрируются с вычислительными решениями
  • Но вообще PostgreSQL неплох

Попробуем Elasticsearch


In [ ]:
import asyncio

import aiohttp

from aioes import Elasticsearch
from datetime import datetime

es = Elasticsearch(['localhost:9200'])

In [ ]:
URL = "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3"

async def create_db():
    async with aiohttp.ClientSession() as session:
        async with session.get(URL) as resp:
            films_urls = (await resp.json())["films"]
            
        for i, film_url in enumerate(films_urls):
            async with session.get(film_url) as resp:
                res = await es.index(
                    index="coding-index",
                    doc_type='film',
                    id=i,
                    body=await resp.json()
                )
                print(res['created'])

loop = asyncio.get_event_loop()
loop.run_until_complete(create_db())

In [ ]:
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html

async def get_by_id(key):
    return await es.get(index='coding-index', doc_type='film', id=key)

async def search_by_director(director):
    return await es.search(index='coding-index', body={"query": {"match": {'director': director}}})

async def search_in_description(sentence):
    return await es.search(index='coding-index', body={"query": {"match": {'description': sentence}}})

In [ ]:
# loop.run_until_complete(get_by_id(0))
# loop.run_until_complete(search_by_director("Hayao Miyazaki"))
loop.run_until_complete(search_in_description("cat"))

Дома поковырять

  • Сделать сервис для хранения заметок и для поиска по ним. Сервис может быть ботом в телеграме, а может быть сайтом.

In [ ]: