Решение задания 1. Создание базы данных
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.")
Логика решения: база создаётся с IF NOT EXISTS, чтобы скрипт можно было запускать повторно. Затем мы явно проверяем наличие базы в списке, чтобы убедиться, что команда отработала.
Решение задания 2. Создание таблиц
import pymysql
config = {
"host": "ich-edit.edu.itcareerhub.de",
"user": "ich1",
"password": "ich1_password_ilovedbs",
}
db_name = "bookstore"
books_query = """
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)
)
"""
users_query = """
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)
)
"""
with pymysql.connect(**config) as connection:
with connection.cursor() as cursor:
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {db_name}")
cursor.execute(f"USE {db_name}")
cursor.execute(books_query)
cursor.execute(users_query)
cursor.execute("SHOW TABLES")
tables = [row[0] for row in cursor]
print(f"Tables in '{db_name}':")
for table in tables:
print(f"- {table}")
Логика решения: сначала создаём базу, затем переключаемся на неё через USE. Таблицы создаём с ограничениями CHECK, чтобы на уровне БД предотвратить отрицательные значения.
Решение задания 3. Загрузка книг из файла
def load_books_from_file(filename: str, connection) -> None:
added = 0
with open(filename, encoding="utf-8") as file:
with connection.cursor() as cursor:
cursor.execute(f"USE bookstore")
for line in file:
parts = line.strip().split(",")
if len(parts) != 4:
continue
title, author, price, stock = parts
price = float(price)
stock = int(stock)
cursor.execute(
"SELECT id, stock FROM books WHERE title = %s AND author = %s",
(title, author),
)
result = cursor.fetchone()
if result:
book_id, current_stock = result
cursor.execute(
"UPDATE books SET stock = %s WHERE id = %s",
(current_stock + stock, book_id),
)
else:
cursor.execute(
"INSERT INTO books (title, author, price, stock) VALUES (%s, %s, %s, %s)",
(title, author, price, stock),
)
added += 1
connection.commit()
print(f"{added} books loaded.")
Логика решения: функция обрабатывает файл построчно. Для каждой строки проверяет существование книги по составному ключу title + author. Если книга найдена — обновляется складской остаток, иначе — вставляется новая запись. commit() вызывается один раз после завершения цикла.
Решение задания 4. Регистрация клиента
def register_user(connection) -> None:
username = input("Enter username: ")
password = input("Enter password: ")
try:
balance = float(input("Enter initial balance: "))
except ValueError:
print("Invalid balance.")
return
with connection.cursor() as cursor:
cursor.execute(f"USE bookstore")
cursor.execute("SELECT id FROM users WHERE username = %s", (username,))
if cursor.fetchone():
print("Username already exists.")
return
cursor.execute(
"INSERT INTO users (username, password, balance) VALUES (%s, %s, %s)",
(username, password, balance),
)
connection.commit()
print("Registration successful.")
Логика решения: guard clause сначала проверяет корректность баланса, затем — уникальность логина. Если логин занят, функция завершается до выполнения INSERT. Это делает код плоским и понятным.
Решение задания 5. Вход в аккаунт и подменю
def try_login(connection) -> int | None:
username = input("Enter username: ")
password = input("Enter password: ")
with connection.cursor() as cursor:
cursor.execute(f"USE bookstore")
cursor.execute(
"SELECT id FROM users WHERE username = %s AND password = %s",
(username, password),
)
result = cursor.fetchone()
if result:
print("Login successful.")
return result[0]
print("Invalid username or password.")
return None
def user_menu(connection, user_id: int) -> None:
while True:
print(f"\n--- User Menu (ID {user_id}) ---")
print("1. View available books")
print("0. Logout")
choice = input("Choose action: ")
if choice == "1":
print("Books will be shown here.")
elif choice == "0":
break
else:
print("Invalid option.")
Логика решения: функция входа возвращает либо идентификатор, либо None. Главное меню проверяет результат и запускает подменю только при успехе. Подменю — отдельный цикл, который прерывается выбором 0.
Решение задания 6 (★). Поиск книг по названию
def search_books(connection) -> None:
keyword = input("Enter keyword: ")
pattern = f"%{keyword}%"
with connection.cursor() as cursor:
cursor.execute(f"USE bookstore")
cursor.execute(
"SELECT id, title, author, price, stock FROM books WHERE title LIKE %s",
(pattern,),
)
books = cursor.fetchall()
if not books:
print("No books found.")
return
for book in books:
book_id, title, author, price, stock = book
print(f"{book_id}. {title} by {author}, ${price}, stock: {stock}")
Логика решения: шаблон для LIKE формируется в Python, но сам запрос остаётся параметризованным. Так мы избегаем SQL-инъекций и при этом получаем гибкий поиск по подстроке.
Решение задания 7 (★). Переменные окружения
import os
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", ""),
}
Пример файла .env:
DB_HOST=ich-edit.edu.itcareerhub.de
DB_USER=ich1
DB_PASSWORD=ich1_password_ilovedbs
Логика решения: секреты загружаются из окружения, а код остаётся безопасным для публикации. Файл .env добавляется в .gitignore, чтобы случайно не попасть в репозиторий.