📜 Как показано в исходном PDF
Исходный материал учебный: все данные для подключения хранятся прямо в коде, функции есть, но программа не разбита на модули. Имя базы данных вставляется f-строкой, потому что это константа, заданная разработчиком.
import pymysql
config = {
"host": "ich-edit.edu.itcareerhub.de",
"user": "ich1",
"password": "ich1_password_ilovedbs",
}
db_name = "bookstore"
with pymysql.connect(**config) as connection:
with connection.cursor() as cursor:
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {db_name}")
cursor.execute("SHOW DATABASES")
databases = [row[0] for row in cursor]
if db_name in databases:
print(f"Database '{db_name}' created or already exists.")
Что здесь происходит: код выполняет свою задачу, но смешивает настройки подключения и бизнес-логику. Для учебного примера это нормально, но в рабочем проекте так делать не стоит.
❌ Почему этот подход может быть не лучшим сейчас
- Пароли в коде. Если файл попадёт в публичный репозиторий, учётные данные станут доступны всем.
- Один большой файл. С ростом проекта становится сложно искать функции и тестировать их по отдельности.
- f-строки в SQL. Даже если сейчас это константа, привычка вставлять переменные в запросы рано или поздно приведёт к SQL-инъекции.
- Нет обработки ошибок. Если сервер недоступен или пароль неверный, программа упадёт с необработанным исключением.
✅ Рекомендуемый современный вариант
Разделим код на три части: настройки, функции для работы с базой и точку входа. Пароль и логин берём из переменных окружения. Все пользовательские данные передаём параметризованными запросами.
import os
import pymysql
from dotenv import load_dotenv
load_dotenv()
DB_CONFIG = {
"host": os.getenv("DB_HOST", "localhost"),
"user": os.getenv("DB_USER", "root"),
"password": os.getenv("DB_PASSWORD", ""),
}
DB_NAME = "bookstore"
def create_database(connection) -> None:
with connection.cursor() as cursor:
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {DB_NAME}")
def create_tables(connection) -> None:
with connection.cursor() as cursor:
cursor.execute(f"USE {DB_NAME}")
cursor.execute("""
CREATE TABLE IF NOT EXISTS books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
author VARCHAR(100),
price DECIMAL(10, 2),
stock INT CHECK (stock >= 0)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100),
password VARCHAR(100),
balance DECIMAL(10, 2) CHECK (balance >= 0)
)
""")
def main() -> None:
try:
with pymysql.connect(**DB_CONFIG) as connection:
create_database(connection)
create_tables(connection)
print("Database and tables are ready.")
except pymysql.MySQLError as e:
print(f"Database error: {e}")
if __name__ == "__main__":
main()
Что улучшилось:
- Секреты вынесены из кода — их можно менять без правки файлов.
- Каждая функция отвечает за одну задачу; их легко тестировать отдельно.
- Обработка ошибок
pymysql.MySQLErrorдаёт понятное сообщение вместо traceback. - Точка входа защищена
if __name__ == "__main__", поэтому функции можно импортировать.
📊 Сравнение подходов
| Аспект | Старый подход | Современный подход |
|---|---|---|
| Хранение секретов | В коде | В переменных окружения / .env |
| Формирование SQL | f-строки | Параметризованные запросы %s |
| Структура проекта | Один файл | Модули: config, db, main |
| Обработка ошибок | Редко или никогда | try/except на уровне подключения |
| Запуск | Выполняется при импорте | Только через if __name__ == "__main__" |
🕰️ Когда старый подход ещё можно встретить
Код с учебными учётными данными и небольшими скриптами до сих пор встречается в:
- учебных материалах и туториалах;
- быстрых прототипах и одноразовых скриптах;
- legacy-проектах, где ещё не провели рефакторинг.
Важно уметь распознавать такой код и постепенно переносить секреты в окружение, разбивать логику на функции и заменять f-строки в SQL на параметризованные запросы.