Перейти к содержанию

212 уроков, 15 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 2 из 20 Начальный 30 мин 100 XP

Подключение к SQLite: create_engine

Строка подключения — это адрес базы. Разбираем sqlite:///:memory: против файла, ленивый движок и журнал echo, который показывает настоящий SQL.

Редакция Питоники

В первом уроке движок появился одной строкой — create_engine("sqlite:///:memory:") — и заработал. Но у этой строки есть анатомия, у движка — характер, а у SQLite — два режима жизни: в памяти и в файле. Сегодня разберём подключение до винтика: куда указывает адрес, когда база реально создаётся и как подсмотреть SQL, который SQLAlchemy сочиняет под капотом.

Строка подключения: адрес базы

Первый аргумент create_engine — строка подключения, URL базы. Схема одна для всех СУБД, которые умеет SQLAlchemy: диал+драйвер://пользователь:пароль@хост:порт/имя_базы. SQLite — единственная безсерверная в этой компании: хоста и пароля у неё нет, есть только путь к файлу.

Строка подключенияЧто это
sqlite:///:memory:SQLite в памяти процесса — исчезает при завершении
sqlite:///tasks.dbSQLite-файл tasks.db в текущей папке
sqlite:////data/tasks.dbSQLite-файл с абсолютным путём (четыре слэша)
postgresql+psycopg://user:пароль@localhost/shopPostgreSQL на сервере localhost
mysql+pymysql://user:пароль@localhost:3306/shopMySQL на сервере, порт 3306
адреса для других СУБД
# PostgreSQL на localhost, база shop
engine = create_engine("postgresql+psycopg://user:пароль@localhost:5432/shop")

# MySQL на сервере, порт 3306
engine = create_engine("mysql+pymysql://user:пароль@localhost:3306/shop")

# SQLite - файл в текущей папке
engine = create_engine("sqlite:///shop.db")
Блок не запускается: для PostgreSQL и MySQL нужен запущенный сервер и драйвер (pip install psycopg / pymysql). В песочнице доступен только sqlite — а код дальше по курсу от выбора диала не меняется.

Один и тот же код на SQLAlchemy работает и с файлом SQLite, и с сервером PostgreSQL — меняется только строка подключения. Поэтому учиться можно на SQLite, а работаете вы хоть с PostgreSQL: модели, сессии и запросы из этого курса переедут без правок. Проверим минимальное подключение и заодно посмотрим на поведение движка.

минимальное подключение
from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///:memory:")

with engine.connect() as conn:
    one = conn.execute(text("SELECT 1")).scalar()
    print(one)
Вывод
1

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

Похоже на магию: одна строка — и база есть. На самом деле create_engine не подключается ни к чему: он только разбирает адрес и готовит фабрику соединений. Настоящее открытие базы случится на первом же запросе. Проверим честно — на файловой базе, средствами os.path.

файл появляется на первом запросе
from sqlalchemy import create_engine, text
import os

engine = create_engine("sqlite:///ghost.db")
print("Движок создан, файла нет:", not os.path.exists("ghost.db"))

with engine.connect() as conn:
    print("Первый запрос:", conn.execute(text("SELECT 1")).scalar())
    print("Файл появился:", os.path.exists("ghost.db"))

engine.dispose()
os.remove("ghost.db")
Вывод
Движок создан, файла нет: True
Первый запрос: 1
Файл появился: True

engine.dispose() закрывает все соединения пула — перед удалением файла это вежливость: открытый файл SQLite на Windows удалить нельзя. Отсюда привычка для скриптов: доработал — dispose — удали, если файл временный.

Файловая база: данные переживают запуск

Режим :memory: из прошлого урока удобен для опытов, но данные там живут ровно один запуск. Файловая база — обычный файл на диске: создал таблицы, записал данные, закрыл программу — всё на месте. Создадим магазинную базу и убедимся, что файл существует.

база в файле market.db
from sqlalchemy import create_engine, text
import os

engine = create_engine("sqlite:///market.db")

with engine.connect() as conn:
    conn.execute(text("CREATE TABLE goods (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)"))
    conn.execute(text("INSERT INTO goods (name, price) VALUES ('степлер', 240)"))
    conn.execute(text("INSERT INTO goods (name, price) VALUES ('папка', 80)"))
    conn.commit()

print("Файл создан:", os.path.exists("market.db"))
engine.dispose()
os.remove("market.db")
print("После удаления:", os.path.exists("market.db"))
Вывод
Файл создан: True
После удаления: False

А теперь главное преимущество файла — перезапуск. Симулируем его в одном скрипте: первая половина записывает данные и «завершается», вторая — новая программа — открывает тот же файл и читает то, что осталось.

перезапуск: данные на месте
from sqlalchemy import create_engine, text
import os

engine = create_engine("sqlite:///market.db")
with engine.connect() as conn:
    conn.execute(text("CREATE TABLE goods (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)"))
    conn.execute(text("INSERT INTO goods (name, price) VALUES ('степлер', 240)"))
    conn.commit()
engine.dispose()

# --- программа завершилась и запустилась заново ---

engine = create_engine("sqlite:///market.db")
with engine.connect() as conn:
    for row in conn.execute(text("SELECT id, name, price FROM goods")):
        print(row)

engine.dispose()
os.remove("market.db")
Вывод
(1, 'степлер', 240)
sqlite:///:memory:sqlite:///файл.db
Скоростьмаксимальная: диск не участвуетобычная для диска
Жизнь данныходин запуск скриптапока файл не удалён
Для чегоуроки, тесты, кэшинастоящие приложения и данные

У каждого движка своя база :memory:

Вернёмся в память за ловушкой. Две базы :memory: в одной программе — это две разные базы: память привязана к движку, а не к строке подключения. Второй движок не увидит ни таблиц, ни данных первого.

второй движок видит пустоту
from sqlalchemy import create_engine, text

alpha = create_engine("sqlite:///:memory:")
beta = create_engine("sqlite:///:memory:")

with alpha.connect() as conn:
    conn.execute(text("CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"))
    conn.commit()

with beta.connect() as conn:
    try:
        conn.execute(text("SELECT * FROM notes"))
    except Exception as e:
        print(type(e).__name__)
        print(str(e).split("\n")[0])
Вывод
OperationalError
(sqlite3.OperationalError) no such table: notes

echo=True: заглядываем под капот

У движка есть выключатель рентгена — echo=True. С ним SQLAlchemy печатает в консоль каждый SQL-запрос, который отправляет в базу: и твой текст, и служебные команды. Вот как выглядит прошлогодний скрипт с заметками под журналом:

тот же скрипт с echo=True
from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///:memory:", echo=True)

with engine.begin() as conn:
    conn.execute(text("CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"))
    conn.execute(text("INSERT INTO notes (body) VALUES ('первая заметка')"))

with engine.connect() as conn:
    rows = conn.execute(text("SELECT id, body FROM notes")).all()
    for row in rows:
        print(row)
Вывод
2026-10-12 19:29:52,966 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2026-10-12 19:29:52,968 INFO sqlalchemy.engine.Engine CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)
2026-10-12 19:29:52,968 INFO sqlalchemy.engine.Engine [generated in 0.00021s] ()
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine INSERT INTO notes (body) VALUES ('первая заметка')
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine [generated in 0.00016s] ()
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine COMMIT
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine SELECT id, body FROM notes
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine [generated in 0.00028s] ()
(1, 'первая заметка')
2026-10-12 19:29:52,969 INFO sqlalchemy.engine.Engine ROLLBACK
Блок не запускается на странице: журнал содержит отметки времени, и они меняются при каждом запуске. Запусти этот код дома — увидишь те же строки со своими датами.

Читается журнал так. BEGIN (implicit) — SQLAlchemy открыла транзакцию сама, ты её не просил. Затем — твои команды, а в скобках после generated in — время сборки SQL. COMMIT в первом блоке появился благодаря engine.begin(): этот контекстный менеджер открывает транзакцию и коммитит её на выходе — короче, чем connect плюс ручной commit. ROLLBACK в конце — соединение пула закрывается без сохранений. И даже print(row) тут затерялся между строк журнала: результат запроса SQLAlchemy тоже печатает.

Отметки времени и слова INFO sqlalchemy.engine.Engine добавляет стандартный модуль logging — SQLAlchemy пишет журнал через него. Настроим журнал сами, чтобы остался чистый SQL: свой обработчик с форматом только из сообщения.

чистый журнал SQL
import sys
import logging

sql_log = logging.getLogger("sqlalchemy.engine")
sql_log.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(message)s"))
sql_log.addHandler(handler)

from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///:memory:")

with engine.begin() as conn:
    conn.execute(text("CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"))
    conn.execute(text("INSERT INTO notes (body) VALUES ('первая заметка')"))

with engine.connect() as conn:
    rows = conn.execute(text("SELECT id, body FROM notes")).all()
    for row in rows:
        print(row)
Вывод
BEGIN (implicit)
CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)
[generated in 0.00014s] ()
INSERT INTO notes (body) VALUES ('первая заметка')
[generated in 0.00009s] ()
COMMIT
BEGIN (implicit)
SELECT id, body FROM notes
[generated in 0.00009s] ()
(1, 'первая заметка')
ROLLBACK

Тот же протокол, без дат: числа в квадратных скобках — microseconds сборки SQL, у тебя они будут своими. Зачем это всё: с урока 3 мы начнём писать запросы классами Python, и единственный способ понять, какой SQL получился на выходе, — вот такой журнал. Он же — первый инструмент отладки, когда запрос возвращает не то, что ты ожидал.

Что дальше

Ты разобрал подключение по косточкам: адрес базы, два режима SQLite, ленивый движок и журнал SQL. База при этом по-прежнему описывается текстом — CREATE TABLE руками. В уроке 3 появляется то, ради чего ORM и берут: таблица как класс Python, с колонками-атрибутами и проверкой типов прямо в редакторе.

Строка подключения — это адрес, движок — фабрика соединений, а настоящая база открывается только на первом запросе.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from sqlalchemy import create_engine, text
import os

engine = create_engine("sqlite:///demo.db")
print("Шаг 1:", os.path.exists("demo.db"))

with engine.connect() as conn:
    conn.execute(text("CREATE TABLE t (id INTEGER PRIMARY KEY)"))
    conn.commit()

print("Шаг 2:", os.path.exists("demo.db"))
engine.dispose()
os.remove("demo.db")
from sqlalchemy import create_engine, text

a = create_engine("sqlite:///:memory:")
b = create_engine("sqlite:///:memory:")

with a.connect() as conn:
    conn.execute(text("CREATE TABLE t (id INTEGER)"))
    conn.commit()

with b.connect() as conn:
    print(conn.execute(text("SELECT COUNT(*) FROM t")).scalar())
from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///:memory:")

with engine.begin() as conn:
    conn.execute(text("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)"))
    conn.execute(text("INSERT INTO t (v) VALUES (:v)"), {"v": "хвост"})

with engine.connect() as conn:
    print(conn.execute(text("SELECT v FROM t")).scalar())
Проверь себя
0 / 5

1. Когда create_engine("sqlite:///market.db") реально создаёт файл базы?

2. Сколько баз :memory: создадут два вызова create_engine("sqlite:///:memory:")?

3. Что делает engine.dispose()?

4. Чем engine.begin() отличается от engine.connect()?

5. Зачем нужен echo=True?

Карточки терминов
Запомнено: 0 / 6
Практика

Создай файловую базу shop.db: таблицу goods (id INTEGER PRIMARY KEY, name TEXT, price INTEGER) и два товара через executemany-вставку со списком параметров — «ручка» за 30 и «тетрадь» за 25. Затем открой базу вторым движком (как перезапуск программы), выведи все строки выборкой по id и удали файл.

practice.py
Вопросы и ответы по уроку

Чем sqlite:///:memory: отличается от sqlite:///file.db?

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

Почему файл базы не появился после create_engine?

Движок ленивый: create_engine только разбирает строку подключения и ничего не открывает. Файл (или память) создаётся при первом запросе через соединение. Проверяется через os.path.exists: False сразу после create_engine и True после первого execute.

Как посмотреть SQL, который SQLAlchemy отправляет в базу?

Включи журнал: create_engine(url, echo=True) печатает каждый запрос с параметрами и служебными BEGIN/COMMIT. Отметки времени в журнале даёт модуль logging — если нужен чистый SQL, добавь к логгеру sqlalchemy.engine свой обработчик с форматом %(message)s. Для отладки — да, в проде журнал выключают: он показывает данные пользователей.

Как перейти с SQLite на PostgreSQL?

Поменять строку подключения на postgresql+psycopg://user:пароль@хост/имя_базы и поставить драйвер: pip install psycopg. Модели, сессии и запросы остаются без изменений — в этом главная причина писать код через SQLAlchemy, а не через sqlite3 напрямую. Синтаксис строк подключения для других СУБД собран в таблице урока.

Понравился урок? Сошлитесь на него

«Один и тот же код на SQLAlchemy работает и с файлом SQLite, и с сервером PostgreSQL — меняется только строка подключения.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.