Урок 88. Презентация проекта
⚡ Кратко: решение
Эталонный скелет проекта — не готовое копирование, а архитектура, которую можно адаптировать.
- Модули отвечают каждый за свою зону: UI, MySQL, логи, статистика, форматирование.
- Каждая функция маленькая и решает одну задачу.
- Обработка ошибок изолирует сбои баз данных от основного цикла.
Что запомнить: хороший скелет проекта — это уже 50% успеха.
Частая ошибка: копировать решение без понимания, почему код устроен именно так.
✅ Эталонный скелет проекта
Ниже — разбор архитектуры и ключевых функций. Это не полное готовое решение, а ориентир, по которому можно построить свой проект.
Архитектура
movie_search/
├── main.py # UI: меню, ввод, вызов других модулей
├── mysql_connector.py # Работа с MySQL: подключение, поиск, справочники
├── log_writer.py # Запись логов в MongoDB
├── log_stats.py # Чтение статистики из MongoDB
└── formatter.py # Красивый вывод в консоль
main.py — только меню и маршрутизация
def main():
while True:
show_menu()
choice = input("Выбор: ").strip()
if choice == "1":
handle_keyword_search()
elif choice == "2":
handle_genre_year_search()
elif choice == "3":
handle_popular_stats()
elif choice == "4":
handle_latest_stats()
elif choice == "0":
print("До свидания!")
break
else:
print("Неверный выбор.")
Здесь нет прямого SQL и MongoDB — только вызовы функций из других модулей.
mysql_connector.py — изоляция базы данных
def search_by_keyword(keyword, limit=10, offset=0):
"""Возвращает список фильмов, название которых содержит keyword."""
...
def search_by_genre_year(genre, year_from, year_to, limit=10, offset=0):
"""Возвращает фильмы жанра genre, выпущенные между годами."""
...
def get_genres():
"""Возвращает отсортированный список жанров."""
...
def get_year_range():
"""Возвращает (min_year, max_year) из таблицы film."""
...
log_writer.py — атомарная запись
def write_log(collection, search_type, params, results_count):
"""Записывает один поисковый запрос в MongoDB."""
doc = {
"timestamp": datetime.now().isoformat(),
"search_type": search_type,
"params": params,
"results_count": results_count,
}
collection.insert_one(doc)
log_stats.py — две агрегации
def most_popular(collection, limit=5):
pipeline = [
{"$group": {"_id": {"type": "$search_type", "params": "$params"}, "count": {"$sum": 1}}},
{"$sort": {"count": -1}},
{"$limit": limit},
]
return list(collection.aggregate(pipeline))
def latest_unique(collection, limit=5):
pipeline = [
{"$sort": {"timestamp": -1}},
{"$group": {"_id": {"type": "$search_type", "params": "$params"}, "timestamp": {"$first": "$timestamp"}}},
{"$sort": {"timestamp": -1}},
{"$limit": limit},
]
return list(collection.aggregate(pipeline))
formatter.py — единый формат вывода
def print_films(films):
if not films:
print("Ничего не найдено.")
return
for film in films:
print(f"{film['film_id']}: {film['title']} ({film['release_year']})")
print(f" {film.get('description', 'Описание отсутствует')}")
print(f"Всего показано: {len(films)}")
Ключевые принципы
- Single Responsibility: каждый модуль отвечает за одну область.
- DRY: подключение к базам, форматирование и обработка ошибок вынесены в одно место.
- Безопасность: SQL-запросы параметризованы.
- Отказоустойчивость: ошибки баз данных не прерывают работу приложения.