From fcca993df55bd3cf565261380eb9a09f1391d1f6 Mon Sep 17 00:00:00 2001 From: 0xSheff <0xsheff@gmail.com> Date: Wed, 28 May 2025 16:36:21 +0000 Subject: [PATCH 1/3] lesson 2 HW - added classes and decorators --- hw-lesson2/__init__.py | 0 hw-lesson2/classes.py | 112 +++++++++++++++++++++++++++++++++++++++++ hw-lesson2/hw-2.py | 111 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 4 files changed, 224 insertions(+) create mode 100644 hw-lesson2/__init__.py create mode 100644 hw-lesson2/classes.py create mode 100644 hw-lesson2/hw-2.py diff --git a/hw-lesson2/__init__.py b/hw-lesson2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hw-lesson2/classes.py b/hw-lesson2/classes.py new file mode 100644 index 0000000..d3f0895 --- /dev/null +++ b/hw-lesson2/classes.py @@ -0,0 +1,112 @@ +from pydantic import BaseModel + + +# Decorators +def log_book_addition(func): + def wrapper(self, book, *args, **kwargs): + if isinstance(book, Book): + print(f"[BOOK_ADDITION] Додання книги: '{book.name}' (Автор: {book.author}, Рік: {book.year})") + return func(self, book, *args, **kwargs) + else: + print(f"[BOOK_ADDITION_ERROR] Спроба додати об'єкт типу {type(book)} до бібліотеки.") + return None + return wrapper + + +def check_book_exists_before_removal(func): + def wrapper(self, book_to_remove, *args, **kwargs): + if not isinstance(book_to_remove, Book): + print(f"[BOOK_REMOVAL_ERROR] Об'єкт для видалення не є книгою (тип: {type(book_to_remove)}). Видалення неможливе.") + return None + + if book_to_remove in self._books: + print(f"[BOOK_REMOVAL] Книга '{book_to_remove.name}' ({book_to_remove.author}) існує. Спроба видалення...") + return func(self, book_to_remove, *args, **kwargs) + else: + print(f"[BOOK_REMOVAL_ERROR] Книгу '{book_to_remove.name}' ({book_to_remove.author}) не знайдено в бібліотеці.") + return None + return wrapper + + +# Classes +class BookModel(BaseModel): + title: str + author: str + year: int + + +class Book: + @property + def name(self) -> str: + return self._book_data.title + + @property + def author(self) -> str: + return self._book_data.author + + @property + def year(self) -> int: + return self._book_data.year + + def __init__(self, book_data: BookModel): + self._book_data = book_data + + def __eq__(self, other): + if not isinstance(other, Book): + return NotImplemented + return (self.name == other.name and + self.author == other.author and + self.year == other.year) + + def __hash__(self): + return hash((self.name, self.author, self.year)) + + def __repr__(self) -> str: + return f"(title='{self.name}', author='{self.author}', year={self.year})" + + def get_info(self) -> str: + return (f"Назва: {self.name}\n" + f"Автор: {self.author}\n" + f"Рік видання: {self.year}") + + +class Library: + def __init__(self, books: list[Book] = None): + if books is None: + self._books: list[Book] = [] + else: + self._books: list[Book] = list(books) + + # iterator + def __iter__(self): + return iter(self._books) + + def __str__(self) -> str: + if not self._books: + return "Бібліотека порожня." + + return f"Книги в бібліотеці: ({len(self._books)} шт.)" + + # generator + def get_books_by_author(self, author_name: str): + found_any = False + for book in self._books: + if book.author == author_name: + yield book + found_any = True + if not found_any: + print(f"Книг автора '{author_name}' не знайдено.") + + @log_book_addition + def add_book(self, book: Book): + self._books.append(book) + + @check_book_exists_before_removal + def remove_book(self, book_to_remove: Book): + self._books.remove(book_to_remove) + print(f"Книгу '{book_to_remove.name}' ({book_to_remove.author}) видалено з бібліотеки.") + + def get_books(self) -> list[Book]: + # Повертаємо копію списку книг + return self._books[:] + diff --git a/hw-lesson2/hw-2.py b/hw-lesson2/hw-2.py new file mode 100644 index 0000000..e5b477a --- /dev/null +++ b/hw-lesson2/hw-2.py @@ -0,0 +1,111 @@ +from classes import BookModel, Book, Library + + +# Examples +if __name__ == '__main__': + # Створення екземплярів BookModel та Book + book_model1 = BookModel(title="Код да Вінчі", author="Ден Браун", year=2003) + book1 = Book(book_model1) + + book_model2 = BookModel(title="1984", author="Джордж Орвелл", year=1949) + book2 = Book(book_model2) + + book_model3 = BookModel(title="Гаррі Поттер і філософський камінь", author="Дж. К. Роулінг", year=1997) + book3 = Book(book_model3) + + book_model4 = BookModel(title="Інферно", author="Ден Браун", year=2013) + book4 = Book(book_model4) + + book_model5 = BookModel(title="Колгосп тварин", author="Джордж Орвелл", year=1945) + book5 = Book(book_model5) + + # Створення бібліотеки + my_library = Library() + print(my_library) # Порожня бібліотека + print("-" * 40) + + # --- Демонстрація декоратора log_book_addition --- + print("Демонстрація додавання книг:") + my_library.add_book(book1) + my_library.add_book(book2) + my_library.add_book(book4) + print("-" * 40) + + print("Спроба додати не Book об'єкт:") + my_library.add_book("Це не книга") # Тестуємо додавання неправильного типу + print("-" * 40) + print("Поточний стан бібліотеки:") + print(my_library) + print("-" * 40) + + # --- Демонстрація декоратора check_book_exists_before_removal --- + print("Демонстрація видалення книг (з перевіркою наявності):") + + # 1. Видалення існуючої книги + print("\nВидалення існуючої книги (book2 - '1984'):") + book_to_remove_existing_data = BookModel(title="1984", author="Джордж Орвелл", year=1949) + book_to_remove_existing = Book(book_to_remove_existing_data) + my_library.remove_book(book_to_remove_existing) + print("Стан бібліотеки після видалення '1984':") + print(my_library) + print("-" * 40) + + # 2. Спроба видалити книгу, якої немає в бібліотеці + print("\nСпроба видалити неіснуючу книгу (book3 - 'Гаррі Поттер'):") + my_library.remove_book(book3) # book3 не додавали, або вже видалили + print("Стан бібліотеки (має бути без змін):") + print(my_library) + print("-" * 40) + + # 3. Спроба видалити книгу, яка щойно була видалена (book2 - '1984') + print("\nСпроба повторно видалити книгу '1984':") + my_library.remove_book(book_to_remove_existing) # Вже видалена + print("Стан бібліотеки (має бути без змін):") + print(my_library) + print("-" * 40) + + # 4. Спроба видалити не Book об'єкт + print("\nСпроба видалити не Book об'єкт:") + my_library.remove_book(12345) + print("Стан бібліотеки (має бути без змін):") + print(my_library) + print("-" * 40) + + # --- Демонстрація ітератора та генератора --- + print("Додамо ще кілька книг для демонстрації ітератора/генератора:") + my_library.add_book(book5) # 'Колгосп тварин' + my_library.add_book(book3) # 'Гаррі Поттер' + print(my_library) + print("-" * 40) + + print("Використання ітератора (for book in my_library):") + for book_item in my_library: + print(f" - {book_item.name} by {book_item.author} ({book_item.year})") + print("-" * 40) + + print("Використання генератора (get_books_by_author):") + print("\nКниги Дена Брауна:") + found_db_books = False + for book_by_author in my_library.get_books_by_author("Ден Браун"): + print(f" Знайдено: {book_by_author.name}") + found_db_books = True + if not found_db_books: + print(" Книг Дена Брауна не знайдено.") + + + print("\nКниги Джорджа Орвелла:") + found_go_books = False + for book_by_author in my_library.get_books_by_author("Джордж Орвелл"): + print(f" Знайдено: {book_by_author.name}") + found_go_books = True + if not found_go_books: + print(" Книг Джорджа Орвелла не знайдено.") + + + print("\nСпроба знайти книги неіснуючого автора:") + found_na_books = False + for book_by_author in my_library.get_books_by_author("Неіснуючий Автор"): + print(f" Знайдено: {book_by_author.name}") # Цей рядок не виконається + found_na_books = True + if not found_na_books: + print(" (Підтвердження: генератор не повернув жодної книги для 'Неіснуючий Автор')") diff --git a/pyproject.toml b/pyproject.toml index cff1c5b..defebd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "pydantic[email] (>=2.11.5,<3.0.0)" ] +[tool.poetry] package-mode = false [build-system] From 9ec7aa36ca0d0337c82ebbb85613aa05308e06f1 Mon Sep 17 00:00:00 2001 From: 0xSheff <0xsheff@gmail.com> Date: Wed, 28 May 2025 21:50:13 +0000 Subject: [PATCH 2/3] lesson 2 HW - added export and import --- hw-lesson2/classes.py | 68 +++++++++++++++++++-- hw-lesson2/hw-2.py | 138 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 175 insertions(+), 31 deletions(-) diff --git a/hw-lesson2/classes.py b/hw-lesson2/classes.py index d3f0895..449f6c6 100644 --- a/hw-lesson2/classes.py +++ b/hw-lesson2/classes.py @@ -1,4 +1,5 @@ from pydantic import BaseModel +import json # Decorators @@ -7,16 +8,13 @@ def wrapper(self, book, *args, **kwargs): if isinstance(book, Book): print(f"[BOOK_ADDITION] Додання книги: '{book.name}' (Автор: {book.author}, Рік: {book.year})") return func(self, book, *args, **kwargs) - else: - print(f"[BOOK_ADDITION_ERROR] Спроба додати об'єкт типу {type(book)} до бібліотеки.") - return None - return wrapper + return None + return wrapper def check_book_exists_before_removal(func): def wrapper(self, book_to_remove, *args, **kwargs): if not isinstance(book_to_remove, Book): - print(f"[BOOK_REMOVAL_ERROR] Об'єкт для видалення не є книгою (тип: {type(book_to_remove)}). Видалення неможливе.") return None if book_to_remove in self._books: @@ -27,6 +25,20 @@ def wrapper(self, book_to_remove, *args, **kwargs): return None return wrapper +def check_book_exists_before_addition(func): + def wrapper(self, book, *args, **kwargs): + if not isinstance(book, Book): + print( + f"[BOOK_ADDITION_ERROR] Об'єкт даного типу неможливо додати до бібліотеки ({type(book)}).") + return None + + if book in self._books: + print(f"[BOOK_ADDITION_ERROR] Книга '{book.name}' ({book.author}) вже є в бібліотеці.") + return None + else: + return func(self, book, *args, **kwargs) + return wrapper + # Classes class BookModel(BaseModel): @@ -97,6 +109,7 @@ def get_books_by_author(self, author_name: str): if not found_any: print(f"Книг автора '{author_name}' не знайдено.") + @check_book_exists_before_addition @log_book_addition def add_book(self, book: Book): self._books.append(book) @@ -110,3 +123,48 @@ def get_books(self) -> list[Book]: # Повертаємо копію списку книг return self._books[:] + def import_from_file(self, path: str): + with FileOpener(path, 'r') as file: + try: + for line in file: + try: + book_data = json.loads(line.strip()) + book_model = BookModel(**book_data) + book = Book(book_model) + self.add_book(book) + except json.JSONDecodeError: + print(f"[IMPORT_ERROR] Неправильний формат JSON в рядку: {line}") + except Exception as e: + print(f"[IMPORT_ERROR] Помилка при імпорті книги: {e}") + except Exception as e: + print(f"[IMPORT_ERROR] Помилка при читанні файлу: {e}") + + def export_to_file(self, path: str): + with FileOpener(path, 'w') as file: + try: + for book in self._books: + book_data = { + "title": book.name, + "author": book.author, + "year": book.year + } + json_line = json.dumps(book_data, ensure_ascii=False) + file.write(json_line + '\n') + except Exception as e: + print(f"[EXPORT_ERROR] Помилка при експорті книг: {e}") + + +class FileOpener: + def __init__(self, filename, mode): + self.filename = filename + self.mode = mode + self.file = None + + def __enter__(self): + self.file = open(self.filename, self.mode, encoding='utf-8') + return self.file + + def __exit__(self, exc_type, exc_value, traceback): + self.file.close() + + diff --git a/hw-lesson2/hw-2.py b/hw-lesson2/hw-2.py index e5b477a..5db1701 100644 --- a/hw-lesson2/hw-2.py +++ b/hw-lesson2/hw-2.py @@ -19,23 +19,31 @@ book_model5 = BookModel(title="Колгосп тварин", author="Джордж Орвелл", year=1945) book5 = Book(book_model5) + # Ім'я файлу для операцій експорту/імпорту + data_file_path = 'library_data.txt' + # Створення бібліотеки my_library = Library() print(my_library) # Порожня бібліотека print("-" * 40) - # --- Демонстрація декоратора log_book_addition --- + # --- Демонстрація декораторів @check_book_exists_before_addition та @log_book_addition --- print("Демонстрація додавання книг:") - my_library.add_book(book1) - my_library.add_book(book2) - my_library.add_book(book4) + my_library.add_book(book1) # Успішне додавання + my_library.add_book(book2) # Успішне додавання + my_library.add_book(book4) # Успішне додавання + print(f"Поточний стан бібліотеки після початкового додавання: {my_library}") + print("-" * 40) + + print("Спроба додати книгу, яка вже існує (book1 - 'Код да Вінчі'):") + my_library.add_book(book1) # Спроба додати дублікат + print(f"Стан бібліотеки після спроби додати дублікат: {my_library} (кількість книг не має змінитися)") + assert len(my_library.get_books()) == 3 # Перевірка, що кількість не змінилась print("-" * 40) print("Спроба додати не Book об'єкт:") my_library.add_book("Це не книга") # Тестуємо додавання неправильного типу - print("-" * 40) - print("Поточний стан бібліотеки:") - print(my_library) + print(f"Стан бібліотеки: {my_library}") print("-" * 40) # --- Демонстрація декоратора check_book_exists_before_removal --- @@ -43,39 +51,32 @@ # 1. Видалення існуючої книги print("\nВидалення існуючої книги (book2 - '1984'):") + # Створюємо новий екземпляр Book для видалення, щоб перевірити роботу __eq__ book_to_remove_existing_data = BookModel(title="1984", author="Джордж Орвелл", year=1949) book_to_remove_existing = Book(book_to_remove_existing_data) my_library.remove_book(book_to_remove_existing) - print("Стан бібліотеки після видалення '1984':") - print(my_library) + print(f"Стан бібліотеки після видалення '1984': {my_library}") print("-" * 40) # 2. Спроба видалити книгу, якої немає в бібліотеці print("\nСпроба видалити неіснуючу книгу (book3 - 'Гаррі Поттер'):") - my_library.remove_book(book3) # book3 не додавали, або вже видалили - print("Стан бібліотеки (має бути без змін):") - print(my_library) + my_library.remove_book(book3) # book3 не додавали до my_library, або вже видалили + print(f"Стан бібліотеки (має бути без змін): {my_library}") print("-" * 40) # 3. Спроба видалити книгу, яка щойно була видалена (book2 - '1984') print("\nСпроба повторно видалити книгу '1984':") my_library.remove_book(book_to_remove_existing) # Вже видалена - print("Стан бібліотеки (має бути без змін):") - print(my_library) + print(f"Стан бібліотеки (має бути без змін): {my_library}") print("-" * 40) - # 4. Спроба видалити не Book об'єкт - print("\nСпроба видалити не Book об'єкт:") - my_library.remove_book(12345) - print("Стан бібліотеки (має бути без змін):") - print(my_library) - print("-" * 40) # --- Демонстрація ітератора та генератора --- print("Додамо ще кілька книг для демонстрації ітератора/генератора:") my_library.add_book(book5) # 'Колгосп тварин' my_library.add_book(book3) # 'Гаррі Поттер' - print(my_library) + # На цей момент my_library містить: book1, book4, book5, book3 + print(f"Поточний стан бібліотеки: {my_library}") print("-" * 40) print("Використання ітератора (for book in my_library):") @@ -92,7 +93,6 @@ if not found_db_books: print(" Книг Дена Брауна не знайдено.") - print("\nКниги Джорджа Орвелла:") found_go_books = False for book_by_author in my_library.get_books_by_author("Джордж Орвелл"): @@ -101,11 +101,97 @@ if not found_go_books: print(" Книг Джорджа Орвелла не знайдено.") - print("\nСпроба знайти книги неіснуючого автора:") found_na_books = False for book_by_author in my_library.get_books_by_author("Неіснуючий Автор"): - print(f" Знайдено: {book_by_author.name}") # Цей рядок не виконається + print(f" Знайдено: {book_by_author.name}") found_na_books = True - if not found_na_books: - print(" (Підтвердження: генератор не повернув жодної книги для 'Неіснуючий Автор')") + print("-" * 40) + + + # --- Демонстрація експорту та імпорту файлів --- + print("Демонстрація експорту та імпорту файлів:") + + # 1. Експорт поточної бібліотеки 'my_library' + # На цей момент my_library містить: book1, book4, book5, book3 (4 книги) + current_books_in_my_library_before_export = list(my_library) + print(f"\n1. Експорт книг з 'my_library' (містить {len(current_books_in_my_library_before_export)} книги):") + print("Книги для експорту:") + for book_item in my_library: + print(f" - '{book_item.name}'") + my_library.export_to_file(data_file_path) + print(f"Книги експортовано у файл '{data_file_path}'.") + print("-" * 40) + + + # 2. Імпорт у нову порожню бібліотеку + print("\n2. Імпорт книг у нову бібліотеку ('imported_library'):") + imported_library = Library() + print(f"Стан 'imported_library' перед імпортом: {imported_library}") + imported_library.import_from_file(data_file_path) + print( + f"\nСтан 'imported_library' після імпорту: {imported_library} (містить {len(imported_library.get_books())} книги)") + print("Книги в 'imported_library':") + if not imported_library.get_books(): + print(" Імпортована бібліотека порожня.") + else: + for book_item in imported_library: + print(f" - '{book_item.name}' by {book_item.author}") + assert len(imported_library.get_books()) == len(current_books_in_my_library_before_export) + print("-" * 40) + + + # 3. Повторний імпорт у 'my_library' (дублікати будуть пропущені декоратором) + print("\n3. Повторний імпорт у 'my_library' (дублікати будуть пропущені):") + initial_book_count_my_library = len(my_library.get_books()) + print(f"Кількість книг у 'my_library' перед повторним імпортом: {initial_book_count_my_library}") + + my_library.import_from_file(data_file_path) + print(f"\nСтан 'my_library' після повторного імпорту: {my_library} (містить {len(my_library.get_books())} книг)") + print(f"Очікувана кількість книг: {initial_book_count_my_library} (не змінилася, бо дублікати пропущено)") + + assert len(my_library.get_books()) == initial_book_count_my_library # Перевірка, що кількість не змінилась + print("-" * 40) + + + # 4. Демонстрація імпорту з файлу з помилками та частковим успіхом (включаючи дублікат) + print("\n4. Демонстрація імпорту з файлу, що містить помилки та дублікати:") + print(f"Створення файлу '{data_file_path}' з некоректними даними...") + + # У файлі буде 3 унікальні коректні книги, 1 дублікат і кілька помилкових рядків + with open(data_file_path, 'w', encoding='utf-8') as f: + f.write('{"title": "Нова Унікальна Книга 1", "author": "Автор Тест Імпорт", "year": 2020}\n') + f.write('Це не JSON рядок.\n') # Помилка JSONDecodeError + f.write('{"title": "Книга з помилкою року", "author": "Автор Тест Помилка", "year": "не_число"}\n') # Помилка Pydantic + f.write('{"title": "Нова Унікальна Книга 2", "author": "Автор Тест Імпорт", "year": 2022}\n') + f.write('{}\n') # Порожній JSON, спричинить помилку Pydantic (відсутні поля) + f.write('{"title": "Книга без автора", "year": 2021}\n') # Помилка Pydantic (відсутнє поле author) + f.write('{"title": "Нова Унікальна Книга 3", "author": "Автор Тест Імпорт", "year": 2023}\n') + f.write('{"title": "Нова Унікальна Книга 1", "author": "Автор Тест Імпорт", "year": 2020}\n') # Дублікат, буде пропущено декоратором + + error_test_library = Library() + print(f"Стан 'error_test_library' перед імпортом з помилками: {error_test_library}") + print(f"\nРозпочинаємо імпорт з файлу '{data_file_path}', що містить помилки та дублікат:") + error_test_library.import_from_file(data_file_path) + + print( + f"\nСтан 'error_test_library' після імпорту: {error_test_library} (містить {len(error_test_library.get_books())} книги)") + print("Книги, які вдалося імпортувати в 'error_test_library':") + imported_count = 0 + if not error_test_library.get_books(): + print(" Жодної книги не було імпортовано.") + else: + for book_item in error_test_library: + print(f" - '{book_item.name}' by {book_item.author}, рік: {book_item.year}") + imported_count +=1 + print(f"(Всього імпортовано коректних та унікальних: {imported_count} книги)") + assert imported_count == 3 # Очікуємо 3 унікальні коректні книги + print("-" * 40) + + # Відновлення файлу data_file_path з поточним станом 'my_library' + # 'my_library' містить 4 книги, які були там до всіх маніпуляцій з файлом у п.4 + print(f"\nВідновлення файлу '{data_file_path}' з поточним станом 'my_library' ({len(my_library.get_books())} книг):") + my_library.export_to_file(data_file_path) # Передаємо шлях до файлу + print(f"Файл '{data_file_path}' оновлено.") + print("Демонстрацію завершено.") + print("-" * 40) \ No newline at end of file From aa754a76e5ef51555114451bd390992f2c3d2823 Mon Sep 17 00:00:00 2001 From: 0xSheff <0xsheff@gmail.com> Date: Sat, 31 May 2025 14:45:19 +0000 Subject: [PATCH 3/3] lesson 2 HW - added Magazine, extended Library functionality --- .gitignore | 1 + hw-lesson2/classes.py | 339 +++++++++++++++++++++++++++++++----------- hw-lesson2/hw-2.py | 271 ++++++++++++++++++++------------- 3 files changed, 422 insertions(+), 189 deletions(-) diff --git a/.gitignore b/.gitignore index 27f1fd8..e314697 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .ruff_cachetry init .idea/ +/hw-lesson2/*.jsonl diff --git a/hw-lesson2/classes.py b/hw-lesson2/classes.py index 449f6c6..e986985 100644 --- a/hw-lesson2/classes.py +++ b/hw-lesson2/classes.py @@ -1,157 +1,331 @@ +from abc import ABC, abstractmethod from pydantic import BaseModel +from typing import List, Iterator, Literal import json -# Decorators -def log_book_addition(func): - def wrapper(self, book, *args, **kwargs): - if isinstance(book, Book): - print(f"[BOOK_ADDITION] Додання книги: '{book.name}' (Автор: {book.author}, Рік: {book.year})") - return func(self, book, *args, **kwargs) - return None - +# --- Decorators --- +def log_item_addition(func): + def wrapper(self, item: PrintableItem, *args, **kwargs): + print(f"[ITEM_ADDITION]: '{item.title}', {item.item_type}") + return func(self, item, *args, **kwargs) return wrapper -def check_book_exists_before_removal(func): - def wrapper(self, book_to_remove, *args, **kwargs): - if not isinstance(book_to_remove, Book): - return None - if book_to_remove in self._books: - print(f"[BOOK_REMOVAL] Книга '{book_to_remove.name}' ({book_to_remove.author}) існує. Спроба видалення...") - return func(self, book_to_remove, *args, **kwargs) +def check_item_exists_before_removal(func): + def wrapper(self, item_to_remove: PrintableItem, *args, **kwargs): + if item_to_remove in self._items: + print(f"[ITEM_REMOVAL] Елемент {item_to_remove.title} існує. Спроба видалення...") + return func(self, item_to_remove, *args, **kwargs) else: - print(f"[BOOK_REMOVAL_ERROR] Книгу '{book_to_remove.name}' ({book_to_remove.author}) не знайдено в бібліотеці.") + print(f"[ITEM_REMOVAL_ERROR] Елемент {item_to_remove.title} не знайдено в бібліотеці.") return None return wrapper -def check_book_exists_before_addition(func): - def wrapper(self, book, *args, **kwargs): - if not isinstance(book, Book): - print( - f"[BOOK_ADDITION_ERROR] Об'єкт даного типу неможливо додати до бібліотеки ({type(book)}).") + +def check_item_exists_before_addition(func): + def wrapper(self, item: PrintableItem, *args, **kwargs): + item_identity = f"'{item.title}'" + + if isinstance(item, Book): + item_identity += f" (Автор: {item.author})" + elif isinstance(item, Magazine): + item_identity += f" (Випуск: {item.issue_number})" + + if item in self._items: + print(f"[ITEM_ADDITION_ERROR] {item.item_type} {item_identity} вже є в бібліотеці.") return None + else: + return func(self, item, *args, **kwargs) + return wrapper - if book in self._books: - print(f"[BOOK_ADDITION_ERROR] Книга '{book.name}' ({book.author}) вже є в бібліотеці.") + +def check_item_has_correct_type(func): + def wrapper(self, item: PrintableItem, *args, **kwargs): + if not isinstance(item, PrintableItem): + print( + f"[ITEM_ADDITION_ERROR] Об'єкт даного типу ({type(item)}) неможливо додати до бібліотеки. " + f"Дозволені лише нащадки PrintableItem." + ) return None else: - return func(self, book, *args, **kwargs) + return func(self, item, *args, **kwargs) return wrapper -# Classes +# --- Pydantic Models --- class BookModel(BaseModel): + item_type: Literal["book"] title: str author: str year: int -class Book: +class MagazineModel(BaseModel): + item_type: Literal["magazine"] + title: str + year: int + issue_number: int + + +# --- Abstract Base Class --- +class PrintableItem(ABC): + @property + @abstractmethod + def item_type(self) -> str: + pass + + @property + @abstractmethod + def title(self) -> str: + pass + + @property + @abstractmethod + def year(self) -> int: + pass + + @abstractmethod + def get_info(self) -> str: + pass + + @abstractmethod + def to_dict(self) -> dict: # Словник для серіалізації в JSON + pass + + @abstractmethod + def __eq__(self, other) -> bool: + pass + + @abstractmethod + def __hash__(self) -> int: + pass + + @abstractmethod + def __repr__(self) -> str: + pass + + +# --- Classes --- +class Book(PrintableItem): + @property + def item_type(self) -> str: + return self.__item_data.item_type + @property - def name(self) -> str: - return self._book_data.title + def title(self) -> str: + return self.__item_data.title @property def author(self) -> str: - return self._book_data.author + return self.__item_data.author @property def year(self) -> int: - return self._book_data.year + return self.__item_data.year - def __init__(self, book_data: BookModel): - self._book_data = book_data + def __init__(self, book: BookModel): + if not isinstance(book, BookModel): + raise ValueError("book_data має бути екземпляром BookModel") + self.__item_data = book - def __eq__(self, other): + def __eq__(self, other) -> bool: if not isinstance(other, Book): return NotImplemented - return (self.name == other.name and + return (self.title == other.title and self.author == other.author and self.year == other.year) - def __hash__(self): - return hash((self.name, self.author, self.year)) + def __hash__(self) -> int: + return hash((self.item_type, self.title, self.author, self.year)) def __repr__(self) -> str: - return f"(title='{self.name}', author='{self.author}', year={self.year})" + return f"Book(title='{self.title}', author='{self.author}', year={self.year})" def get_info(self) -> str: - return (f"Назва: {self.name}\n" + return (f"Тип: {self.item_type}\n" + f"Назва: {self.title}\n" f"Автор: {self.author}\n" f"Рік видання: {self.year}") + def to_dict(self) -> dict: + return { + "item_type": self.item_type, + "title": self.title, + "author": self.author, + "year": self.year + } + + +class Magazine(PrintableItem): + @property + def item_type(self) -> str: + return self.__item_data.item_type + + @property + def title(self) -> str: + return self.__item_data.title + + @property + def year(self) -> int: + return self.__item_data.year + + @property + def issue_number(self) -> int: + return self.__item_data.issue_number + + def __init__(self, magazine: MagazineModel): + if not isinstance(magazine, MagazineModel): + raise ValueError("magazine має бути екземпляром MagazineModel") + self.__item_data = magazine + + def __eq__(self, other) -> bool: + if not isinstance(other, Magazine): + return NotImplemented + return (self.title == other.title and + self.year == other.year and + self.issue_number == other.issue_number) + + def __hash__(self) -> int: + return hash((self.item_type, self.title, self.year, self.issue_number)) + + def __repr__(self) -> str: + return f"Magazine(title='{self.title}', year={self.year}, issue={self.issue_number})" + + def get_info(self) -> str: + return (f"Тип: {self.item_type}\n" + f"Назва: {self.title}\n" + f"Рік видання: {self.year}\n" + f"Номер випуску: {self.issue_number}") + + def to_dict(self) -> dict: + return { + "item_type": self.item_type, + "title": self.title, + "year": self.year, + "issue_number": self.issue_number + } + class Library: - def __init__(self, books: list[Book] = None): - if books is None: - self._books: list[Book] = [] + def __init__(self, items: List[PrintableItem] = None): + if items is None: + self._items: List[PrintableItem] = [] else: - self._books: list[Book] = list(books) + self._items: List[PrintableItem] = [ + item for item in items if isinstance(item, PrintableItem) + ] + if len(self._items) != len(items if items else []): + print("[LIBRARY_INIT_WARNING] Деякі елементи не були додані під час ініціалізації, оскільки не є екземплярами PrintableItem.") - # iterator - def __iter__(self): - return iter(self._books) + def __iter__(self) -> Iterator[PrintableItem]: + return iter(self._items) def __str__(self) -> str: - if not self._books: + if not self._items: return "Бібліотека порожня." - return f"Книги в бібліотеці: ({len(self._books)} шт.)" + num_books = sum(1 for item in self._items if isinstance(item, Book)) + num_magazines = sum(1 for item in self._items if isinstance(item, Magazine)) + total_items = len(self._items) - # generator - def get_books_by_author(self, author_name: str): + return (f"Елементи в бібліотеці: ({total_items} шт.)\n" + f" Книги: {num_books}\n" + f" Журнали: {num_magazines}") + + def get_books_by_author(self, author_name: str) -> Iterator[Book]: found_any = False - for book in self._books: - if book.author == author_name: - yield book + for item in self._items: + if isinstance(item, Book) and item.author == author_name: + yield item found_any = True if not found_any: print(f"Книг автора '{author_name}' не знайдено.") - @check_book_exists_before_addition - @log_book_addition - def add_book(self, book: Book): - self._books.append(book) - - @check_book_exists_before_removal - def remove_book(self, book_to_remove: Book): - self._books.remove(book_to_remove) - print(f"Книгу '{book_to_remove.name}' ({book_to_remove.author}) видалено з бібліотеки.") + def get_magazines_by_year(self, year: int) -> Iterator[Magazine]: + found_any = False + for item in self._items: + if isinstance(item, Magazine) and item.year == year: + yield item + found_any = True + if not found_any: + print(f"Журналів за {year} рік не знайдено.") - def get_books(self) -> list[Book]: - # Повертаємо копію списку книг - return self._books[:] + def get_items_by_title(self, title: str) -> Iterator[PrintableItem]: + found_any = False + for item in self._items: + if item.title == title: + yield item + found_any = True + if not found_any: + print(f"Елементів з назвою '{title}' не знайдено.") + + @check_item_has_correct_type + @check_item_exists_before_addition + @log_item_addition + def add_item(self, item: PrintableItem): + self._items.append(item) + + @check_item_exists_before_removal + def remove_item(self, item_to_remove: PrintableItem): + self._items.remove(item_to_remove) + + item_type_str = item_to_remove.item_type + details = f"'{item_to_remove.title}'" + if isinstance(item_to_remove, Book): + item_type_str = "Книгу" + details += f" (Автор: {item_to_remove.author})" + elif isinstance(item_to_remove, Magazine): + item_type_str = "Журнал" + details += f" (Випуск: {item_to_remove.issue_number})" + print(f"{item_type_str} {details} видалено з бібліотеки.") + + def get_items(self) -> List[PrintableItem]: + return self._items[:] def import_from_file(self, path: str): with FileOpener(path, 'r') as file: try: - for line in file: + for line_number, line in enumerate(file, 1): + stripped_line = line.strip() + if not stripped_line: + continue # Пропустити порожні рядки try: - book_data = json.loads(line.strip()) - book_model = BookModel(**book_data) - book = Book(book_model) - self.add_book(book) + data = json.loads(stripped_line) + json_item_type = data.get("item_type") + + if json_item_type == "magazine": + model = MagazineModel(**data) + item_instance = Magazine(model) + elif json_item_type == "book": + model = BookModel(**data) + item_instance = Book(model) + else: + print( + f"[IMPORT_ERROR] Невідомий 'item_type': '{json_item_type}' в рядку: {stripped_line}") + continue + + if item_instance: + self.add_item(item_instance) + except json.JSONDecodeError: - print(f"[IMPORT_ERROR] Неправильний формат JSON в рядку: {line}") + print(f"[IMPORT_ERROR] Неправильний формат JSON в рядку: {stripped_line}") except Exception as e: - print(f"[IMPORT_ERROR] Помилка при імпорті книги: {e}") + print( + f"[IMPORT_ERROR] Помилка при обробці елемента з рядка '{stripped_line}': {e}") except Exception as e: - print(f"[IMPORT_ERROR] Помилка при читанні файлу: {e}") + print(f"[IMPORT_ERROR] Помилка при читанні файлу '{path}': {e}") def export_to_file(self, path: str): with FileOpener(path, 'w') as file: try: - for book in self._books: - book_data = { - "title": book.name, - "author": book.author, - "year": book.year - } - json_line = json.dumps(book_data, ensure_ascii=False) + for item in self._items: + item_dict = item.to_dict() + json_line = json.dumps(item_dict, ensure_ascii=False) file.write(json_line + '\n') + print(f"[EXPORT_SUCCESS] Дані успішно експортовано до файлу '{path}'.") except Exception as e: - print(f"[EXPORT_ERROR] Помилка при експорті книг: {e}") + print(f"[EXPORT_ERROR] Помилка при експорті елементів до файлу '{path}': {e}") class FileOpener: @@ -165,6 +339,5 @@ def __enter__(self): return self.file def __exit__(self, exc_type, exc_value, traceback): - self.file.close() - - + if self.file: + self.file.close() diff --git a/hw-lesson2/hw-2.py b/hw-lesson2/hw-2.py index 5db1701..d1d6447 100644 --- a/hw-lesson2/hw-2.py +++ b/hw-lesson2/hw-2.py @@ -1,197 +1,256 @@ -from classes import BookModel, Book, Library - +from classes import BookModel, Book, MagazineModel, Magazine, Library, PrintableItem # Examples if __name__ == '__main__': - # Створення екземплярів BookModel та Book - book_model1 = BookModel(title="Код да Вінчі", author="Ден Браун", year=2003) + # --- Створення екземплярів BookModel та Book --- + book_model1 = BookModel(item_type="book", title="Код да Вінчі", author="Ден Браун", year=2003) book1 = Book(book_model1) - book_model2 = BookModel(title="1984", author="Джордж Орвелл", year=1949) + book_model2 = BookModel(item_type="book", title="1984", author="Джордж Орвелл", year=1949) book2 = Book(book_model2) - book_model3 = BookModel(title="Гаррі Поттер і філософський камінь", author="Дж. К. Роулінг", year=1997) + book_model3 = BookModel(item_type="book", title="Гаррі Поттер і філософський камінь", author="Дж. К. Роулінг", year=1997) book3 = Book(book_model3) - book_model4 = BookModel(title="Інферно", author="Ден Браун", year=2013) + book_model4 = BookModel(item_type="book", title="Інферно", author="Ден Браун", year=2013) book4 = Book(book_model4) - book_model5 = BookModel(title="Колгосп тварин", author="Джордж Орвелл", year=1945) + book_model5 = BookModel(item_type="book", title="Колгосп тварин", author="Джордж Орвелл", year=1945) book5 = Book(book_model5) - # Ім'я файлу для операцій експорту/імпорту - data_file_path = 'library_data.txt' + # --- Створення екземплярів MagazineModel та Magazine --- + magazine_model1 = MagazineModel(item_type="magazine", title="National Geographic", year=2023, issue_number=10) + magazine1 = Magazine(magazine_model1) + + magazine_model2 = MagazineModel(item_type="magazine", title="Vogue", year=2023, issue_number=12) + magazine2 = Magazine(magazine_model2) + + magazine_model3 = MagazineModel(item_type="magazine", title="National Geographic", year=2022, issue_number=5) + magazine3 = Magazine(magazine_model3) + + + data_file_path = 'library_data.jsonl' - # Створення бібліотеки my_library = Library() print(my_library) # Порожня бібліотека print("-" * 40) - # --- Демонстрація декораторів @check_book_exists_before_addition та @log_book_addition --- - print("Демонстрація додавання книг:") - my_library.add_book(book1) # Успішне додавання - my_library.add_book(book2) # Успішне додавання - my_library.add_book(book4) # Успішне додавання - print(f"Поточний стан бібліотеки після початкового додавання: {my_library}") + # --- Демонстрація декораторів @check_item_exists_before_addition та @log_item_addition --- + print("Демонстрація додавання елементів (книг та журналів):") + my_library.add_item(book1) + my_library.add_item(magazine1) + my_library.add_item(book2) + my_library.add_item(book4) + my_library.add_item(magazine3) + print(f"Поточний стан бібліотеки після початкового додавання:\n{my_library}") print("-" * 40) - print("Спроба додати книгу, яка вже існує (book1 - 'Код да Вінчі'):") - my_library.add_book(book1) # Спроба додати дублікат - print(f"Стан бібліотеки після спроби додати дублікат: {my_library} (кількість книг не має змінитися)") - assert len(my_library.get_books()) == 3 # Перевірка, що кількість не змінилась + print("Спроба додати елемент, який вже існує (book1 - 'Код да Вінчі'):") + my_library.add_item(book1) # Спроба додати дублікат книги + print("Спроба додати елемент, який вже існує (magazine1 - 'National Geographic', 2023, випуск 10):") + my_library.add_item(magazine1) # Спроба додати дублікат журналу + print(f"Стан бібліотеки після спроби додати дублікати:\n{my_library}") + # Перевірка, що кількість не змінилась (3 книги + 2 журнали = 5 елементів) + assert len(my_library.get_items()) == 5 print("-" * 40) - print("Спроба додати не Book об'єкт:") - my_library.add_book("Це не книга") # Тестуємо додавання неправильного типу - print(f"Стан бібліотеки: {my_library}") + print("Спроба додати не PrintableItem об'єкт:") + my_library.add_item("Це не книга і не журнал") # Тестуємо додавання неправильного типу + print(f"Стан бібліотеки:\n{my_library}") print("-" * 40) - # --- Демонстрація декоратора check_book_exists_before_removal --- - print("Демонстрація видалення книг (з перевіркою наявності):") + # --- Демонстрація декоратора check_item_exists_before_removal --- + print("Демонстрація видалення елементів (з перевіркою наявності):") # 1. Видалення існуючої книги print("\nВидалення існуючої книги (book2 - '1984'):") - # Створюємо новий екземпляр Book для видалення, щоб перевірити роботу __eq__ - book_to_remove_existing_data = BookModel(title="1984", author="Джордж Орвелл", year=1949) + book_to_remove_existing_data = BookModel(item_type="book", title="1984", author="Джордж Орвелл", year=1949) book_to_remove_existing = Book(book_to_remove_existing_data) - my_library.remove_book(book_to_remove_existing) - print(f"Стан бібліотеки після видалення '1984': {my_library}") + my_library.remove_item(book_to_remove_existing) + print(f"Стан бібліотеки після видалення '1984':\n{my_library}") + print("-" * 40) + + # 2. Видалення існуючого журналу + print("\nВидалення існуючого журналу (magazine3 - 'National Geographic', 2022, випуск 5):") + magazine_to_remove_existing_data = MagazineModel(item_type="magazine", title="National Geographic", year=2022, issue_number=5) + magazine_to_remove_existing = Magazine(magazine_to_remove_existing_data) + my_library.remove_item(magazine_to_remove_existing) + print(f"Стан бібліотеки після видалення журналу 'National Geographic' (2022):\n{my_library}") print("-" * 40) - # 2. Спроба видалити книгу, якої немає в бібліотеці + # 3. Спроба видалити елемент, якого немає в бібліотеці (книга) print("\nСпроба видалити неіснуючу книгу (book3 - 'Гаррі Поттер'):") - my_library.remove_book(book3) # book3 не додавали до my_library, або вже видалили - print(f"Стан бібліотеки (має бути без змін): {my_library}") + my_library.remove_item(book3) # book3 не додавали до my_library, або вже видалили + print(f"Стан бібліотеки (має бути без змін):\n{my_library}") print("-" * 40) - # 3. Спроба видалити книгу, яка щойно була видалена (book2 - '1984') + # 4. Спроба видалити елемент, який щойно був видалений (book2 - '1984') print("\nСпроба повторно видалити книгу '1984':") - my_library.remove_book(book_to_remove_existing) # Вже видалена - print(f"Стан бібліотеки (має бути без змін): {my_library}") + my_library.remove_item(book_to_remove_existing) # Вже видалена + print(f"Стан бібліотеки (має бути без змін):\n{my_library}") print("-" * 40) - # --- Демонстрація ітератора та генератора --- - print("Додамо ще кілька книг для демонстрації ітератора/генератора:") - my_library.add_book(book5) # 'Колгосп тварин' - my_library.add_book(book3) # 'Гаррі Поттер' - # На цей момент my_library містить: book1, book4, book5, book3 - print(f"Поточний стан бібліотеки: {my_library}") + # --- Демонстрація ітератора та генераторів --- + print("Додамо ще кілька елементів для демонстрації ітератора/генераторів:") + my_library.add_item(book5) # 'Колгосп тварин' + my_library.add_item(book3) # 'Гаррі Поттер' + my_library.add_item(magazine2) # 'Vogue' + # На цей момент my_library містить: book1, book4, magazine1, book5, book3, magazine2 + print(f"Поточний стан бібліотеки:\n{my_library}") print("-" * 40) - print("Використання ітератора (for book in my_library):") - for book_item in my_library: - print(f" - {book_item.name} by {book_item.author} ({book_item.year})") + print("Використання ітератора (for item in my_library):") + print("Книги:") + for item in my_library: + if isinstance(item, Book): + print(f" - '{item.title}' by {item.author} ({item.year})") + else: + continue + + print("Журнали:") + for item in my_library: + if isinstance(item, Magazine): + print(f" - '{item.title}', рік: {item.year}, випуск: {item.issue_number}") + else: + continue print("-" * 40) - print("Використання генератора (get_books_by_author):") + print("Використання генератора get_books_by_author:") print("\nКниги Дена Брауна:") - found_db_books = False for book_by_author in my_library.get_books_by_author("Ден Браун"): - print(f" Знайдено: {book_by_author.name}") - found_db_books = True - if not found_db_books: - print(" Книг Дена Брауна не знайдено.") + print(f" Знайдено: {book_by_author.title}") print("\nКниги Джорджа Орвелла:") - found_go_books = False for book_by_author in my_library.get_books_by_author("Джордж Орвелл"): - print(f" Знайдено: {book_by_author.name}") - found_go_books = True - if not found_go_books: - print(" Книг Джорджа Орвелла не знайдено.") + print(f" Знайдено: {book_by_author.title}") + print("-" * 40) + + print("Використання генератора get_magazines_by_year:") + print("\nЖурнали за 2023 рік:") + for mag_by_year in my_library.get_magazines_by_year(2023): + print(f" Знайдено: {mag_by_year.title}, випуск: {mag_by_year.issue_number}") + + print("\nЖурнали за 2021 рік (немає):") + for mag_by_year in my_library.get_magazines_by_year(2021): + print(f" Знайдено: {mag_by_year.title}, випуск: {mag_by_year.issue_number}") + print("-" * 40) - print("\nСпроба знайти книги неіснуючого автора:") - found_na_books = False - for book_by_author in my_library.get_books_by_author("Неіснуючий Автор"): - print(f" Знайдено: {book_by_author.name}") - found_na_books = True + print("Використання генератора get_items_by_title:") + print("\nЕлементи з назвою 'National Geographic':") + for item_by_title in my_library.get_items_by_title("National Geographic"): + if isinstance(item_by_title, Magazine): + print(f" Знайдено журнал: {item_by_title.title}, рік: {item_by_title.year}, випуск: {item_by_title.issue_number}") + else: + print(f" Знайдено: {item_by_title.title} ({item_by_title.item_type})") + + print("\nЕлементи з назвою 'Неіснуюча Назва':") + for item_by_title in my_library.get_items_by_title("Неіснуюча Назва"): # Цей цикл не виконається + print(f" Знайдено: {item_by_title.title}") print("-" * 40) # --- Демонстрація експорту та імпорту файлів --- print("Демонстрація експорту та імпорту файлів:") - - # 1. Експорт поточної бібліотеки 'my_library' - # На цей момент my_library містить: book1, book4, book5, book3 (4 книги) - current_books_in_my_library_before_export = list(my_library) - print(f"\n1. Експорт книг з 'my_library' (містить {len(current_books_in_my_library_before_export)} книги):") - print("Книги для експорту:") - for book_item in my_library: - print(f" - '{book_item.name}'") + # На цей момент my_library містить: book1, magazine1, book4, book5, book3, magazine2 (6 елементів) + current_items_in_my_library_before_export = list(my_library) + print(f"\n1. Експорт елементів з 'my_library' (містить {len(current_items_in_my_library_before_export)} елементи):") + print("Елементи для експорту:") + for item in my_library: + print(f" - '{item.title}' ({item.item_type})") my_library.export_to_file(data_file_path) - print(f"Книги експортовано у файл '{data_file_path}'.") + print(f"Елементи експортовано у файл '{data_file_path}'.") print("-" * 40) # 2. Імпорт у нову порожню бібліотеку - print("\n2. Імпорт книг у нову бібліотеку ('imported_library'):") + print("\n2. Імпорт елементів у нову бібліотеку ('imported_library'):") imported_library = Library() - print(f"Стан 'imported_library' перед імпортом: {imported_library}") + print(f"Стан 'imported_library' перед імпортом:\n{imported_library}") imported_library.import_from_file(data_file_path) print( - f"\nСтан 'imported_library' після імпорту: {imported_library} (містить {len(imported_library.get_books())} книги)") - print("Книги в 'imported_library':") - if not imported_library.get_books(): + f"\nСтан 'imported_library' після імпорту:\n{imported_library} (містить {len(imported_library.get_items())} елементи)") + print("Елементи в 'imported_library':") + if not imported_library.get_items(): print(" Імпортована бібліотека порожня.") else: - for book_item in imported_library: - print(f" - '{book_item.name}' by {book_item.author}") - assert len(imported_library.get_books()) == len(current_books_in_my_library_before_export) + for item in imported_library: + if isinstance(item, Book): + print(f" - Книга: '{item.title}' by {item.author}") + elif isinstance(item, Magazine): + print(f" - Журнал: '{item.title}', рік: {item.year}, випуск: {item.issue_number}") + assert len(imported_library.get_items()) == len(current_items_in_my_library_before_export) print("-" * 40) # 3. Повторний імпорт у 'my_library' (дублікати будуть пропущені декоратором) print("\n3. Повторний імпорт у 'my_library' (дублікати будуть пропущені):") - initial_book_count_my_library = len(my_library.get_books()) - print(f"Кількість книг у 'my_library' перед повторним імпортом: {initial_book_count_my_library}") + initial_item_count_my_library = len(my_library.get_items()) + print(f"Кількість елементів у 'my_library' перед повторним імпортом: {initial_item_count_my_library}") my_library.import_from_file(data_file_path) - print(f"\nСтан 'my_library' після повторного імпорту: {my_library} (містить {len(my_library.get_books())} книг)") - print(f"Очікувана кількість книг: {initial_book_count_my_library} (не змінилася, бо дублікати пропущено)") + print(f"\nСтан 'my_library' після повторного імпорту:\n{my_library} (містить {len(my_library.get_items())} елементів)") + print(f"Очікувана кількість елементів: {initial_item_count_my_library} (не змінилася, бо дублікати пропущено)") - assert len(my_library.get_books()) == initial_book_count_my_library # Перевірка, що кількість не змінилась + assert len(my_library.get_items()) == initial_item_count_my_library print("-" * 40) - # 4. Демонстрація імпорту з файлу з помилками та частковим успіхом (включаючи дублікат) + # 4. Демонстрація імпорту з файлу з помилками та частковим успіхом print("\n4. Демонстрація імпорту з файлу, що містить помилки та дублікати:") print(f"Створення файлу '{data_file_path}' з некоректними даними...") - # У файлі буде 3 унікальні коректні книги, 1 дублікат і кілька помилкових рядків + # У файлі буде: + # 1. Коректна книга 1 (унікальна) + # 2. Коректний журнал 1 (унікальний) + # 3. Рядок, що не є JSON + # 4. Книга з помилкою валідації Pydantic (рік - не число) + # 5. Журнал з помилкою валідації Pydantic (відсутній номер випуску) + # 6. Коректна книга 2 (унікальна) + # 7. Порожній JSON {} (спричинить помилку Pydantic) + # 8. JSON з невідомим item_type + # 9. Коректний журнал 2 (унікальний) + # 10. Дублікат книги 1 (буде пропущено) + # 11. Дублікат журналу 1 (буде пропущено) + # Очікується імпорт: Книга 1, Журнал 1, Книга 2, Журнал 2 (всього 4 елементи) with open(data_file_path, 'w', encoding='utf-8') as f: - f.write('{"title": "Нова Унікальна Книга 1", "author": "Автор Тест Імпорт", "year": 2020}\n') - f.write('Це не JSON рядок.\n') # Помилка JSONDecodeError - f.write('{"title": "Книга з помилкою року", "author": "Автор Тест Помилка", "year": "не_число"}\n') # Помилка Pydantic - f.write('{"title": "Нова Унікальна Книга 2", "author": "Автор Тест Імпорт", "year": 2022}\n') - f.write('{}\n') # Порожній JSON, спричинить помилку Pydantic (відсутні поля) - f.write('{"title": "Книга без автора", "year": 2021}\n') # Помилка Pydantic (відсутнє поле author) - f.write('{"title": "Нова Унікальна Книга 3", "author": "Автор Тест Імпорт", "year": 2023}\n') - f.write('{"title": "Нова Унікальна Книга 1", "author": "Автор Тест Імпорт", "year": 2020}\n') # Дублікат, буде пропущено декоратором + f.write('{"item_type": "book", "title": "Унікальна Книга Імпорт 1", "author": "Автор Тест Імпорт", "year": 2020}\n') + f.write('{"item_type": "magazine", "title": "Унікальний Журнал Імпорт 1", "year": 2021, "issue_number": 1}\n') + f.write('Це не JSON рядок.\n') + f.write('{"item_type": "book", "title": "Книга з помилкою року", "author": "Автор Тест Помилка", "year": "не_число"}\n') + f.write('{"item_type": "magazine", "title": "Журнал без номера", "year": 2022}\n') # Відсутній issue_number + f.write('{"item_type": "book", "title": "Унікальна Книга Імпорт 2", "author": "Автор Тест Імпорт", "year": 2022}\n') + f.write('{}\n') + f.write('{"item_type": "newspaper", "title": "Газета", "year": 2023}\n') # Невідомий item_type + f.write('{"item_type": "magazine", "title": "Унікальний Журнал Імпорт 2", "year": 2023, "issue_number": 2}\n') + f.write('{"item_type": "book", "title": "Унікальна Книга Імпорт 1", "author": "Автор Тест Імпорт", "year": 2020}\n') # Дублікат + f.write('{"item_type": "magazine", "title": "Унікальний Журнал Імпорт 1", "year": 2021, "issue_number": 1}\n') # Дублікат error_test_library = Library() - print(f"Стан 'error_test_library' перед імпортом з помилками: {error_test_library}") + print(f"Стан 'error_test_library' перед імпортом з помилками:\n{error_test_library}") print(f"\nРозпочинаємо імпорт з файлу '{data_file_path}', що містить помилки та дублікат:") error_test_library.import_from_file(data_file_path) print( - f"\nСтан 'error_test_library' після імпорту: {error_test_library} (містить {len(error_test_library.get_books())} книги)") - print("Книги, які вдалося імпортувати в 'error_test_library':") + f"\nСтан 'error_test_library' після імпорту:\n{error_test_library} (містить {len(error_test_library.get_items())} елементи)") + print("Елементи, які вдалося імпортувати в 'error_test_library':") imported_count = 0 - if not error_test_library.get_books(): - print(" Жодної книги не було імпортовано.") + if not error_test_library.get_items(): + print(" Жодного елемента не було імпортовано.") else: - for book_item in error_test_library: - print(f" - '{book_item.name}' by {book_item.author}, рік: {book_item.year}") + for item in error_test_library: + if isinstance(item, Book): + print(f" - Книга: '{item.title}' by {item.author}, рік: {item.year}") + elif isinstance(item, Magazine): + print(f" - Журнал: '{item.title}', рік: {item.year}, випуск: {item.issue_number}") imported_count +=1 - print(f"(Всього імпортовано коректних та унікальних: {imported_count} книги)") - assert imported_count == 3 # Очікуємо 3 унікальні коректні книги + print(f"(Всього імпортовано коректних та унікальних: {imported_count} елементи)") + assert imported_count == 4 # Очікуємо 4 унікальні коректні елементи print("-" * 40) # Відновлення файлу data_file_path з поточним станом 'my_library' - # 'my_library' містить 4 книги, які були там до всіх маніпуляцій з файлом у п.4 - print(f"\nВідновлення файлу '{data_file_path}' з поточним станом 'my_library' ({len(my_library.get_books())} книг):") - my_library.export_to_file(data_file_path) # Передаємо шлях до файлу + print(f"\nВідновлення файлу '{data_file_path}' з поточним станом 'my_library' ({len(my_library.get_items())} елементів):") + my_library.export_to_file(data_file_path) print(f"Файл '{data_file_path}' оновлено.") print("Демонстрацію завершено.") - print("-" * 40) \ No newline at end of file + print("-" * 40)