⚖️ Старый и современный подход

⚡ Различия в двух словах

Старый: весь код в одном файле, учебные пароли в словаре config, SQL часто собирается через f-строки.

Новый: параметризованные запросы, разделение на функции и модули, секреты в переменных окружения, точка входа через if __name__ == "__main__".

Вывод: современный подход безопаснее, проще тестируется и легче поддерживается.

📜 Как показано в исходном 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
Формирование SQLf-строкиПараметризованные запросы %s
Структура проектаОдин файлМодули: config, db, main
Обработка ошибокРедко или никогдаtry/except на уровне подключения
ЗапускВыполняется при импортеТолько через if __name__ == "__main__"

🕰️ Когда старый подход ещё можно встретить

Код с учебными учётными данными и небольшими скриптами до сих пор встречается в:

  • учебных материалах и туториалах;
  • быстрых прототипах и одноразовых скриптах;
  • legacy-проектах, где ещё не провели рефакторинг.

Важно уметь распознавать такой код и постепенно переносить секреты в окружение, разбивать логику на функции и заменять f-строки в SQL на параметризованные запросы.