Подключение к SQLite: create_engine
Строка подключения — это адрес базы. Разбираем sqlite:///:memory: против файла, ленивый движок и журнал echo, который показывает настоящий SQL.
Редакция Питоники
В первом уроке движок появился одной строкой — create_engine("sqlite:///:memory:") — и заработал. Но у этой строки есть анатомия, у движка — характер, а у SQLite — два режима жизни: в памяти и в файле. Сегодня разберём подключение до винтика: куда указывает адрес, когда база реально создаётся и как подсмотреть SQL, который SQLAlchemy сочиняет под капотом.
Строка подключения: адрес базы
Первый аргумент create_engine — строка подключения, URL базы. Схема одна для всех СУБД, которые умеет SQLAlchemy: диал+драйвер://пользователь:пароль@хост:порт/имя_базы. SQLite — единственная безсерверная в этой компании: хоста и пароля у неё нет, есть только путь к файлу.
| Строка подключения | Что это |
|---|---|
| sqlite:///:memory: | SQLite в памяти процесса — исчезает при завершении |
| sqlite:///tasks.db | SQLite-файл tasks.db в текущей папке |
| sqlite:////data/tasks.db | SQLite-файл с абсолютным путём (четыре слэша) |
| postgresql+psycopg://user:пароль@localhost/shop | PostgreSQL на сервере localhost |
| mysql+pymysql://user:пароль@localhost:3306/shop | MySQL на сервере, порт 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")
Один и тот же код на 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: из прошлого урока удобен для опытов, но данные там живут ровно один запуск. Файловая база — обычный файл на диске: создал таблицы, записал данные, закрыл программу — всё на месте. Создадим магазинную базу и убедимся, что файл существует.
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-запрос, который отправляет в базу: и твой текст, и служебные команды. Вот как выглядит прошлогодний скрипт с заметками под журналом:
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: свой обработчик с форматом только из сообщения.
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())
1. Когда create_engine("sqlite:///market.db") реально создаёт файл базы?
2. Сколько баз :memory: создадут два вызова create_engine("sqlite:///:memory:")?
3. Что делает engine.dispose()?
4. Чем engine.begin() отличается от engine.connect()?
5. Зачем нужен echo=True?
Создай файловую базу shop.db: таблицу goods (id INTEGER PRIMARY KEY, name TEXT, price INTEGER) и два товара через executemany-вставку со списком параметров — «ручка» за 30 и «тетрадь» за 25. Затем открой базу вторым движком (как перезапуск программы), выведи все строки выборкой по id и удали файл.
Чем 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-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
SQLAlchemy · Урок 1
Что такое SQLAlchemy и ORM: первый движок
SQLAlchemy переводит классы Python в таблицы SQL и обратно. Создаём первый движок и выполняем первые запросы — всё прямо в браузере, без установки.
SQLAlchemy · Урок 3
Модели: декларативные таблицы
Таблица как класс Python: Base, Mapped-аннотации и mapped_column. Модель — это и код, и схема, и документация одновременно.
sqlite3 · Урок 1
SQL с нуля через Python: первая база SQLite и таблица
Первая настоящая база данных без установки серверов: import sqlite3, курсор, CREATE TABLE и первые строки — всё исполняется прямо на странице.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
FastAPI · Урок 7
Подключаем базу данных SQLite к FastAPI
Словарь задач из урока 5 умирает при перезапуске. Ставим на его место SQLite через SQLAlchemy: движок, сессии, Depends и CRUD, который переживает uvicorn --reload.
fastapi база данныхsqlalchemy fastapi
Flask · Урок 5
База данных в Flask: SQLite и Flask-SQLAlchemy
Пятый урок курса Flask: данные переживают перезапуск сервера. Подключаем SQLite, описываем модели Flask-SQLAlchemy, проходим CRUD и собираем гостевую книгу, которая помнит всех гостей.
flask и база данных sqlalchemyflask sqlalchemy модель
SQLAlchemy · Урок 15
SQLAlchemy во Flask: паттерн Flask-SQLAlchemy
Во Flask ORM не меняется — меняется его упаковка: db.Model вместо Base, db.session вместо ручной сессии, а закрытие соединения берёт на себя фреймворк.
flask sqlalchemyflask sqlalchemy паттерн