diff --git a/HW/README.md b/HW/README.md new file mode 100644 index 0000000..db2d276 --- /dev/null +++ b/HW/README.md @@ -0,0 +1,118 @@ +# Library Management System + +## Опис + +Цей проєкт реалізує систему управління бібліотекою, використовуючи сучасні концепції Python: + +Ітератори та генератори для зручного обходу колекцій книг і журналів. + +Декоратори для логування та перевірки дій. + +Контекстні менеджери для роботи з файлами. + +Обʼєктно-орієнтоване програмування (інкапсуляція, спадкування, поліморфізм, абстрактні класи). + +Валідація даних за допомогою Pydantic. + +## Особливості + +Класи Book і Journal – наслідуються від абстрактного класу Publication. + +Pydantic-модель BookModel для валідації даних книги або журналу. + +Ітератор для проходження всіх публікацій у бібліотеці. + +Генератор для пошуку публікацій за конкретним автором. + +Декоратори: + +log_addition – логування додавання нових публікацій у файл library.log. + +check_existence – перевірка наявності публікації перед її видаленням. + +Контекстний менеджер LibraryFileManager – автоматичне завантаження та збереження бібліотеки у файл library.json. + +Логування всіх важливих дій у файл library.log у форматі UTF-8. + +## Як працює скрипт: + +- Створюється обʼєкт бібліотеки. +- Створюються екземпляри книг та журналів. +- Додаються у бібліотеку. +- Виводиться список усіх публікацій. +- Можна переглянути публікації певного автора. +- Бібліотека автоматично зберігається у library.json. +- Видаляються книги/журнали за назвою (видалення спочатку у памʼяті, а потім – при збереженні у файл). +- Завантажуються дані з library.json для відновлення бібліотеки. +- Фінальний список публікацій демонструє стан бібліотеки. + +## Реалізація + +- Модель BookModel (Pydantic) + +Валідація атрибутів: title, author, year. + +- Класи Book, Journal + +Наслідування від абстрактного класу Publication. + +Реалізація методу info() для відображення даних. + +Приватні атрибути для інкапсуляції. + +- Клас Library + +Зберігає список публікацій. + +Ітератор для проходження всіх публікацій. + +Генератор books_by_author для пошуку. + +Методи add_book() і remove_book(). + +Поліморфізм – підтримка як книг, так і журналів. + +- Декоратори + +log_addition: логування додавання у library.log. + +check_existence: перевірка існування перед видаленням і логування помилок. + +- Контекстний менеджер LibraryFileManager + +Завантажує бібліотеку з файлу при вході у контекст. + +Зберігає оновлену бібліотеку при виході. + +Файлова робота (JSON) + +Серіалізація/десеріалізація з урахуванням типу публікації (type). + +- Логування + +Усі дії та помилки записуються у library.log з часовими мітками. + +## Файли + +main.py – головний скрипт з реалізацією. + +library.json – файл для збереження бібліотеки у форматі JSON. + +library.log – журнал усіх дій і помилок (створюється автоматично). + +## Логування + +Всі операції (додавання, видалення, помилки) записуються у library.log з часовими позначками. +Файл логів створюється і оновлюється автоматично. + +## Відповідність вимогам + +Валідація даних – Pydantic. + +Ітератори та генератори. + +Декоратори для логування і перевірок. + +Контекстний менеджер для роботи з файлами. + +Принципи ООП: інкапсуляція, спадкування, поліморфізм, абстрактні класи. \ No newline at end of file diff --git a/HW/library.json b/HW/library.json new file mode 100644 index 0000000..0187f55 --- /dev/null +++ b/HW/library.json @@ -0,0 +1,26 @@ +[ + { + "title": "Науковий журнал", + "author": "Іван Іванов", + "year": 2024, + "type": "journal" + }, + { + "title": "Берестечко", + "author": "Ліна Костенко", + "year": 2010, + "type": "book" + }, + { + "title": "Сад нетанучих скульптур", + "author": "Ліна Костенко", + "year": 2019, + "type": "book" + }, + { + "title": "Мистецтво війни", + "author": "Сунь-Цзи", + "year": 500, + "type": "book" + } +] \ No newline at end of file diff --git a/HW/library.log b/HW/library.log new file mode 100644 index 0000000..dbe7a1c --- /dev/null +++ b/HW/library.log @@ -0,0 +1,30 @@ +2025-05-27 21:12:22,533 | INFO | [LOG] Додаємо публікацію: Мистецтво війни +2025-05-27 21:12:22,533 | INFO | [LOG] Додаємо публікацію: Науковий журнал +2025-05-27 21:12:22,533 | INFO | [LOG] Додаємо публікацію: Берестечко +2025-05-27 21:12:22,533 | INFO | [LOG] Додаємо публікацію: Сад нетанучих скульптур +2025-05-27 21:12:22,533 | INFO | [INFO] Публікацію 'Мистецтво війни' видалено з бібліотеки. +2025-05-27 21:12:22,535 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:12:22,551 | INFO | [INFO] Бібліотека завантажена з файлу 'library.json'. +2025-05-27 21:12:22,551 | WARNING | [WARNING] Публікація 'Мистецтво війни' не знайдена в бібліотеці. +2025-05-27 21:12:22,553 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:12:22,553 | INFO | [INFO] Завершено роботу з файлом бібліотеки. +2025-05-27 21:12:22,554 | INFO | [LOG] Додаємо публікацію: Мистецтво війни +2025-05-27 21:12:22,555 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:12:22,567 | INFO | [INFO] Бібліотека завантажена з файлу 'library.json'. +2025-05-27 21:12:22,569 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:12:22,569 | INFO | [INFO] Завершено роботу з файлом бібліотеки. +2025-05-27 21:14:09,315 | INFO | [LOG] Додаємо публікацію: Мистецтво війни +2025-05-27 21:14:09,315 | INFO | [LOG] Додаємо публікацію: Науковий журнал +2025-05-27 21:14:09,315 | INFO | [LOG] Додаємо публікацію: Берестечко +2025-05-27 21:14:09,316 | INFO | [LOG] Додаємо публікацію: Сад нетанучих скульптур +2025-05-27 21:14:09,316 | INFO | [INFO] Публікацію 'Мистецтво війни' видалено з бібліотеки. +2025-05-27 21:14:09,318 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:14:09,322 | INFO | [INFO] Бібліотека завантажена з файлу 'library.json'. +2025-05-27 21:14:09,322 | WARNING | [WARNING] Публікація 'Мистецтво війни' не знайдена в бібліотеці. +2025-05-27 21:14:09,325 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:14:09,325 | INFO | [INFO] Завершено роботу з файлом бібліотеки. +2025-05-27 21:14:09,326 | INFO | [LOG] Додаємо публікацію: Мистецтво війни +2025-05-27 21:14:09,327 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:14:09,330 | INFO | [INFO] Бібліотека завантажена з файлу 'library.json'. +2025-05-27 21:14:09,332 | INFO | [INFO] Бібліотека збережена у файл 'library.json'. +2025-05-27 21:14:09,332 | INFO | [INFO] Завершено роботу з файлом бібліотеки. diff --git a/HW/main.py b/HW/main.py new file mode 100644 index 0000000..e015936 --- /dev/null +++ b/HW/main.py @@ -0,0 +1,187 @@ +from pydantic import BaseModel +from abc import ABC, abstractmethod +from typing import Generator, Iterator +from contextlib import contextmanager +import logging +import json + +# Налаштування логування у файл +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + filename="library.log", + filemode="a", # додаємо до існуючого + encoding="utf-8", # для української мови +) +logger = logging.getLogger(__name__) + +# Pydantic-модель +class BookModel(BaseModel): + title: str + author: str + year: int + type: str + + +# Абстрактний клас Publication +class Publication(ABC): + def __init__(self, model: BookModel) -> None: + self._model = model + + @property + def model(self) -> BookModel: + return self._model + + @abstractmethod + def info(self) -> str: + pass + + +# Клас Book +class Book(Publication): + def info(self) -> str: + return f"Книга: '{self.model.title}', автор: {self.model.author}, рік: {self.model.year}" + + +# Клас Journal +class Journal(Publication): + def info(self) -> str: + return f"Журнал: '{self.model.title}', автор: {self.model.author}, рік: {self.model.year}" + + +# Декоратор для логування +def log_action(func): + def wrapper(self, book: Publication): + logger.info(f"[LOG] Додаємо публікацію: {book.model.title}") + return func(self, book) + return wrapper + + +# Декоратор для перевірки перед видаленням +def check_book_exists(func): + def wrapper(self, title: str): + if not any(b.model.title == title for b in self._books): + logger.warning(f"[WARNING] Публікація '{title}' не знайдена в бібліотеці.") + return + return func(self, title) + return wrapper + + +# Клас Library +class Library: + def __init__(self): + self._books: list[Publication] = [] + + @log_action + def add_book(self, book: Publication) -> None: + self._books.append(book) + + # Внутрішній метод без логування (для завантаження з файлу) + def _add_book_directly(self, book: Publication) -> None: + self._books.append(book) + + @check_book_exists + def remove_book(self, title: str) -> None: + self._books = [b for b in self._books if b.model.title != title] + logger.info(f"[INFO] Публікацію '{title}' видалено з бібліотеки.") + + def __iter__(self) -> Iterator[Publication]: + return iter(self._books) + + def books_by_author(self, author: str) -> Generator[Publication, None, None]: + return (b for b in self._books if b.model.author == author) + + def save_to_file(self, filename: str) -> None: + with open(filename, "w", encoding="utf-8") as f: + json.dump( + [ + {**b.model.model_dump(), "type": "journal" if isinstance(b, Journal) else "book"} + for b in self._books + ], + f, + ensure_ascii=False, + indent=4 + ) + logger.info(f"[INFO] Бібліотека збережена у файл '{filename}'.") + + def load_from_file(self, filename: str) -> None: + with open(filename, "r", encoding="utf-8") as f: + data = json.load(f) + self._books.clear() + for item in data: + if item["type"] == "journal": + self._add_book_directly(Journal(BookModel(**item))) + else: + self._add_book_directly(Book(BookModel(**item))) + logger.info(f"[INFO] Бібліотека завантажена з файлу '{filename}'.") + + +# Контекстний менеджер +@contextmanager +def LibraryFileManager(library: Library, filename: str): + try: + library.load_from_file(filename) + yield library + finally: + library.save_to_file(filename) + logger.info(f"[INFO] Завершено роботу з файлом бібліотеки.") + +# ============================================================================ + +# Основний блок +if __name__ == "__main__": + lib = Library() + + # Створення книги та журналу + book_01 = Book(BookModel(title="Мистецтво війни", author="Сунь-Цзи", year=500, type="book")) + journal_01 = Journal(BookModel(title="Науковий журнал", author="Іван Іванов", year=2024, type="journal")) + book_02 = Book(BookModel(title="Берестечко", author="Ліна Костенко", year=2010, type="book")) + book_03 = Book(BookModel(title="Сад нетанучих скульптур", author="Ліна Костенко", year=2019, type="book")) + + # Додавання + lib.add_book(book_01) + lib.add_book(journal_01) + lib.add_book(book_02) + lib.add_book(book_03) + + # Видалення книги з пам'яті (RAM) без збереження у файл + lib.remove_book("Мистецтво війни") + print("\nСписок публікацій після видалення книги з пам'яті (без збереження у файл):") + for pub in lib: + print(pub.info()) + + # Збереження до файлу + lib.save_to_file("library.json") + + # Виведення всіх публікацій + print("\nСписок всіх публікацій:") + for pub in lib: + print(pub.info()) + + # Виведення книг по автору + print("\nПублікації Ліна Костенко:") + for pub in lib.books_by_author("Ліна Костенко"): + print(pub.info()) + + # Видалення книги та збереження у файлі + with LibraryFileManager(lib, "library.json") as library: + library.remove_book("Мистецтво війни") + print("\nСписок публікацій після видалення книги:") + for pub in library: + print(pub.info()) + + # Виведення всіх публікацій + print("\nСписок всіх публікацій:") + for pub in lib: + print(pub.info()) + + # Додавання книги «Мистецтво війни» назад + book_04 = Book(BookModel(title="Мистецтво війни", author="Сунь-Цзи", year=500, type="book")) + lib.add_book(book_04) + lib.save_to_file("library.json") + + # Завантаження з файлу + with LibraryFileManager(lib, "library.json") as loaded_library: + print("\nСписок публікацій після завантаження з файлу:") + for pub in loaded_library: + print(pub.info())