From 9b5de8f37e032b3a71ef50f0c4d36cbff836bc19 Mon Sep 17 00:00:00 2001 From: Eric Camplin Date: Wed, 4 Jun 2025 15:43:20 -0700 Subject: [PATCH 1/4] M2 Python - working starter code --- .../application_core/entities/author.py | 6 + .../library/application_core/entities/book.py | 13 + .../application_core/entities/book_item.py | 12 + .../library/application_core/entities/loan.py | 16 + .../application_core/entities/patron.py | 13 + .../enums/loan_extension_status.py | 9 + .../enums/loan_return_status.py | 7 + .../enums/membership_renewal_status.py | 8 + .../interfaces/iloan_repository.py | 12 + .../interfaces/iloan_service.py | 12 + .../interfaces/ipatron_repository.py | 16 + .../interfaces/ipatron_service.py | 7 + .../application_core/services/loan_service.py | 41 ++ .../services/patron_service.py | 23 + .../library/console/common_actions.py | 11 + .../library/console/console_app.py | 161 +++++++ .../library/console/console_state.py | 8 + .../library/console/main.py | 25 ++ .../library/infrastructure/Json/Authors.json | 22 + .../infrastructure/Json/BookItems.json | 22 + .../library/infrastructure/Json/Books.json | 22 + .../library/infrastructure/Json/Loans.json | 402 ++++++++++++++++++ .../library/infrastructure/Json/Patrons.json | 52 +++ .../library/infrastructure/json_data.py | 105 +++++ .../infrastructure/json_loan_repository.py | 24 ++ .../infrastructure/json_patron_repository.py | 26 ++ .../library/tests/test_loan_service.py | 32 ++ .../library/tests/test_patron_service.py | 30 ++ 28 files changed, 1137 insertions(+) create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/author.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book_item.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/loan.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/patron.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_extension_status.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_return_status.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/membership_renewal_status.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_repository.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_service.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_repository.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_service.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/loan_service.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/patron_service.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_app.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_state.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/main.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Authors.json create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/BookItems.json create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Books.json create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Patrons.json create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_data.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_loan_repository.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_patron_repository.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_loan_service.py create mode 100644 DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_patron_service.py diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/author.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/author.py new file mode 100644 index 0000000..dec0e83 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/author.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + +@dataclass +class Author: + id: int + name: str diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book.py new file mode 100644 index 0000000..50f4e38 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from typing import Optional +from .author import Author + +@dataclass +class Book: + id: int + title: str + author_id: int + genre: str + image_name: str + isbn: str + author: Optional[Author] = None diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book_item.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book_item.py new file mode 100644 index 0000000..f5f7fb7 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/book_item.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from typing import Optional +from datetime import datetime +from .book import Book + +@dataclass +class BookItem: + id: int + book_id: int + acquisition_date: datetime + condition: Optional[str] = None + book: Optional[Book] = None diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/loan.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/loan.py new file mode 100644 index 0000000..51955ea --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/loan.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import Optional +from datetime import datetime +from .patron import Patron +from .book_item import BookItem + +@dataclass +class Loan: + id: int + book_item_id: int + patron_id: int + patron: Optional[Patron] = None + loan_date: datetime = None + due_date: datetime = None + return_date: Optional[datetime] = None + book_item: Optional[BookItem] = None diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/patron.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/patron.py new file mode 100644 index 0000000..98e5096 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/entities/patron.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass, field +from typing import List, Optional +from datetime import datetime +# from .loan import Loan # Use string annotation to avoid circular import + +@dataclass +class Patron: + id: int + name: str + membership_end: datetime + membership_start: datetime + image_name: Optional[str] = None + loans: List['Loan'] = field(default_factory=list) diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_extension_status.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_extension_status.py new file mode 100644 index 0000000..20cf2c5 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_extension_status.py @@ -0,0 +1,9 @@ +from enum import Enum + +class LoanExtensionStatus(Enum): + SUCCESS = 'Book loan extension was successful.' + LOAN_NOT_FOUND = 'Loan not found.' + LOAN_EXPIRED = 'Cannot extend book loan as it already has expired. Return the book instead.' + MEMBERSHIP_EXPIRED = "Cannot extend book loan due to expired patron's membership." + LOAN_RETURNED = 'Cannot extend book loan as the book is already returned.' + ERROR = 'Cannot extend book loan due to an error.' diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_return_status.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_return_status.py new file mode 100644 index 0000000..5f9221a --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/loan_return_status.py @@ -0,0 +1,7 @@ +from enum import Enum + +class LoanReturnStatus(Enum): + SUCCESS = 'Book was successfully returned.' + LOAN_NOT_FOUND = 'Loan not found.' + ALREADY_RETURNED = 'Cannot return book as the book is already returned.' + ERROR = 'Cannot return book due to an error.' diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/membership_renewal_status.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/membership_renewal_status.py new file mode 100644 index 0000000..e36433e --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/enums/membership_renewal_status.py @@ -0,0 +1,8 @@ +from enum import Enum + +class MembershipRenewalStatus(Enum): + SUCCESS = 'Membership renewal was successful.' + PATRON_NOT_FOUND = 'Patron not found.' + TOO_EARLY_TO_RENEW = 'It is too early to renew the membership.' + LOAN_NOT_RETURNED = 'Cannot renew membership due to an outstanding loan.' + ERROR = 'Cannot renew membership due to an error.' diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_repository.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_repository.py new file mode 100644 index 0000000..d02d022 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_repository.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from typing import Optional +from ..entities.loan import Loan + +class ILoanRepository(ABC): + @abstractmethod + def get_loan(self, loan_id: int) -> Optional[Loan]: + pass + + @abstractmethod + def update_loan(self, loan: Loan) -> None: + pass diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_service.py new file mode 100644 index 0000000..a34c633 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/iloan_service.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from ..enums.loan_return_status import LoanReturnStatus +from ..enums.loan_extension_status import LoanExtensionStatus + +class ILoanService(ABC): + @abstractmethod + def return_loan(self, loan_id: int) -> LoanReturnStatus: + pass + + @abstractmethod + def extend_loan(self, loan_id: int) -> LoanExtensionStatus: + pass diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_repository.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_repository.py new file mode 100644 index 0000000..57305f1 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_repository.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from typing import List, Optional +from ..entities.patron import Patron + +class IPatronRepository(ABC): + @abstractmethod + def get_patron(self, patron_id: int) -> Optional[Patron]: + pass + + @abstractmethod + def search_patrons(self, search_input: str) -> List[Patron]: + pass + + @abstractmethod + def update_patron(self, patron: Patron) -> None: + pass diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_service.py new file mode 100644 index 0000000..e20199f --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/interfaces/ipatron_service.py @@ -0,0 +1,7 @@ +from abc import ABC, abstractmethod +from ..enums.membership_renewal_status import MembershipRenewalStatus + +class IPatronService(ABC): + @abstractmethod + def renew_membership(self, patron_id: int) -> MembershipRenewalStatus: + pass diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/loan_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/loan_service.py new file mode 100644 index 0000000..0a10307 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/loan_service.py @@ -0,0 +1,41 @@ +from ..interfaces.iloan_service import ILoanService +from ..interfaces.iloan_repository import ILoanRepository +from ..enums.loan_return_status import LoanReturnStatus +from ..enums.loan_extension_status import LoanExtensionStatus +from datetime import datetime, timedelta + +class LoanService(ILoanService): + EXTEND_BY_DAYS = 14 + + def __init__(self, loan_repository: ILoanRepository): + self._loan_repository = loan_repository + + def return_loan(self, loan_id: int) -> LoanReturnStatus: + loan = self._loan_repository.get_loan(loan_id) + if loan is None: + return LoanReturnStatus.LOAN_NOT_FOUND + if loan.return_date is not None: + return LoanReturnStatus.ALREADY_RETURNED + loan.return_date = datetime.now() + try: + self._loan_repository.update_loan(loan) + return LoanReturnStatus.SUCCESS + except Exception: + return LoanReturnStatus.ERROR + + def extend_loan(self, loan_id: int) -> LoanExtensionStatus: + loan = self._loan_repository.get_loan(loan_id) + if loan is None: + return LoanExtensionStatus.LOAN_NOT_FOUND + if loan.patron and loan.patron.membership_end < datetime.now(): + return LoanExtensionStatus.MEMBERSHIP_EXPIRED + if loan.return_date is not None: + return LoanExtensionStatus.LOAN_RETURNED + if loan.due_date < datetime.now(): + return LoanExtensionStatus.LOAN_EXPIRED + try: + loan.due_date = loan.due_date + timedelta(days=self.EXTEND_BY_DAYS) + self._loan_repository.update_loan(loan) + return LoanExtensionStatus.SUCCESS + except Exception: + return LoanExtensionStatus.ERROR diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/patron_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/patron_service.py new file mode 100644 index 0000000..44279e9 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/application_core/services/patron_service.py @@ -0,0 +1,23 @@ +from ..interfaces.ipatron_service import IPatronService +from ..interfaces.ipatron_repository import IPatronRepository +from ..enums.membership_renewal_status import MembershipRenewalStatus +from datetime import datetime, timedelta + +class PatronService(IPatronService): + def __init__(self, patron_repository: IPatronRepository): + self._patron_repository = patron_repository + + def renew_membership(self, patron_id: int) -> MembershipRenewalStatus: + patron = self._patron_repository.get_patron(patron_id) + if patron is None: + return MembershipRenewalStatus.PATRON_NOT_FOUND + if patron.membership_end >= datetime.now() + timedelta(days=30): + return MembershipRenewalStatus.TOO_EARLY_TO_RENEW + if any(l.return_date is None and l.due_date < datetime.now() for l in getattr(patron, 'loans', [])): + return MembershipRenewalStatus.LOAN_NOT_RETURNED + patron.membership_end = patron.membership_end + timedelta(days=365) + try: + self._patron_repository.update_patron(patron) + return MembershipRenewalStatus.SUCCESS + except Exception: + return MembershipRenewalStatus.ERROR diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py new file mode 100644 index 0000000..1d846e6 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py @@ -0,0 +1,11 @@ +from enum import Flag, auto + +class CommonActions(Flag): + REPEAT = 0 + SELECT = auto() + QUIT = auto() + SEARCH_PATRONS = auto() + RENEW_PATRON_MEMBERSHIP = auto() + RETURN_LOANED_BOOK = auto() + EXTEND_LOANED_BOOK = auto() + SEARCH_BOOKS = auto() diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_app.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_app.py new file mode 100644 index 0000000..c7b97a0 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_app.py @@ -0,0 +1,161 @@ +from .console_state import ConsoleState +from .common_actions import CommonActions +from application_core.interfaces.ipatron_repository import IPatronRepository +from application_core.interfaces.iloan_repository import ILoanRepository +from application_core.interfaces.iloan_service import ILoanService +from application_core.interfaces.ipatron_service import IPatronService + +class ConsoleApp: + def __init__( + self, + loan_service: ILoanService, + patron_service: IPatronService, + patron_repository: IPatronRepository, + loan_repository: ILoanRepository + ): + self._current_state: ConsoleState = ConsoleState.PATRON_SEARCH + self.matching_patrons = [] + self.selected_patron_details = None + self.selected_loan_details = None + self._patron_repository = patron_repository + self._loan_repository = loan_repository + self._loan_service = loan_service + self._patron_service = patron_service + + def run(self) -> None: + while True: + if self._current_state == ConsoleState.PATRON_SEARCH: + self._current_state = self.patron_search() + elif self._current_state == ConsoleState.PATRON_SEARCH_RESULTS: + self._current_state = self.patron_search_results() + elif self._current_state == ConsoleState.PATRON_DETAILS: + self._current_state = self.patron_details() + elif self._current_state == ConsoleState.LOAN_DETAILS: + self._current_state = self.loan_details() + elif self._current_state == ConsoleState.QUIT: + break + + def patron_search(self) -> ConsoleState: + search_input = input("Enter a string to search for patrons by name: ").strip() + if not search_input: + print("No input provided. Please try again.") + return ConsoleState.PATRON_SEARCH + self.matching_patrons = self._patron_repository.search_patrons(search_input) + if not self.matching_patrons: + print("No matching patrons found.") + return ConsoleState.PATRON_SEARCH + print("Matching Patrons:") + for idx, patron in enumerate(self.matching_patrons, 1): + print(f"{idx}) {patron.name}") + return ConsoleState.PATRON_SEARCH_RESULTS + + def patron_search_results(self) -> ConsoleState: + print("\nInput Options:") + print(" - Type a number to select a patron from the list") + print(" - Type 's' to search again") + print(" - Type 'q' to quit") + selection = input("Enter your choice: ").strip().lower() + if selection == 'q': + return ConsoleState.QUIT + elif selection == 's': + return ConsoleState.PATRON_SEARCH + elif selection.isdigit(): + idx = int(selection) + if 1 <= idx <= len(self.matching_patrons): + self.selected_patron_details = self.matching_patrons[idx - 1] + return ConsoleState.PATRON_DETAILS + else: + print("Invalid selection. Please enter a valid number.") + return ConsoleState.PATRON_SEARCH_RESULTS + else: + print("Invalid input. Please enter a number, 's', or 'q'.") + return ConsoleState.PATRON_SEARCH_RESULTS + + def patron_details(self) -> ConsoleState: + patron = self.selected_patron_details + print(f"\nName: {patron.name}") + print(f"Membership Expiration: {patron.membership_end}") + loans = self._loan_repository.get_loans_by_patron_id(patron.id) + print("\nBook Loans:") + + # Filter and display valid loans + valid_loans = [] + for idx, loan in enumerate(loans, 1): + if not getattr(loan, 'book_item', None) or not getattr(loan.book_item, 'book', None): + print(f"{idx}) [Invalid loan data: missing book information]") + else: + returned = "True" if getattr(loan, 'return_date', None) else "False" + print(f"{idx}) {loan.book_item.book.title} - Due: {loan.due_date} - Returned: {returned}") + valid_loans.append((idx, loan)) + if valid_loans: + print("Type a number to select a loan from the list") + if not valid_loans: + print("No valid loans for this patron.") + print("Input Options:") + print(" - Type 's' to search again") + print(" - Type 'q' to quit") + selection = input("Enter your choice: ").strip().lower() + if selection == 'q': + return ConsoleState.QUIT + elif selection == 's': + return ConsoleState.PATRON_SEARCH + else: + print("Invalid input.") + return ConsoleState.PATRON_DETAILS + else: + print("Input Options:") + print(" - Type 'm' to renew membership") + print(" - Type 's' to search again") + print(" - Type 'q' to quit") + selection = input("Enter your choice: ").strip().lower() + if selection == 'q': + return ConsoleState.QUIT + elif selection == 's': + return ConsoleState.PATRON_SEARCH + elif selection == 'm': + status = self._patron_service.renew_membership(patron.id) + print(status) + self.selected_patron_details = self._patron_repository.get_patron(patron.id) + return ConsoleState.PATRON_DETAILS + elif selection.isdigit(): + idx = int(selection) + if 1 <= idx <= len(valid_loans): + self.selected_loan_details = valid_loans[idx - 1][1] + return ConsoleState.LOAN_DETAILS + print("Invalid selection. Please enter a number shown in the list above.") + return ConsoleState.PATRON_DETAILS + else: + print("Invalid input. Please enter a number, 'm', 's', or 'q'.") + return ConsoleState.PATRON_DETAILS + + def loan_details(self) -> ConsoleState: + loan = self.selected_loan_details + print(f"\nBook title: {loan.book_item.book.title}") + print(f"Book Author: {loan.book_item.book.author.name}") + print(f"Due date: {loan.due_date}") + returned = "True" if getattr(loan, 'return_date', None) else "False" + print(f"Returned: {returned}\n") + print("Input Options:") + print(" - Type 'r' to return book") + print(" - Type 'e' to extend loan") + print(" - Type 's' to search again") + print(" - Type 'q' to quit") + selection = input("Enter your choice: ").strip().lower() + if selection == 'q': + return ConsoleState.QUIT + elif selection == 's': + return ConsoleState.PATRON_SEARCH + elif selection == 'r': + status = self._loan_service.return_loan(loan.id) + print("Book was successfully returned.") + print(status) + self.selected_loan_details = self._loan_repository.get_loan(loan.id) + return ConsoleState.LOAN_DETAILS + elif selection == 'e': + status = self._loan_service.extend_loan(loan.id) + print(status) + self.selected_loan_details = self._loan_repository.get_loan(loan.id) + return ConsoleState.LOAN_DETAILS + else: + print("Invalid input.") + return ConsoleState.LOAN_DETAILS diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_state.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_state.py new file mode 100644 index 0000000..714335a --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/console_state.py @@ -0,0 +1,8 @@ +from enum import Enum + +class ConsoleState(Enum): + PATRON_SEARCH = 1 + PATRON_SEARCH_RESULTS = 2 + PATRON_DETAILS = 3 + LOAN_DETAILS = 4 + QUIT = 5 diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/main.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/main.py new file mode 100644 index 0000000..f8ca682 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/main.py @@ -0,0 +1,25 @@ +import sys +from pathlib import Path + +# Add the parent directory to sys.path +sys.path.append(str(Path(__file__).resolve().parent.parent)) + +from application_core.services.loan_service import LoanService +from application_core.services.patron_service import PatronService +from infrastructure.json_data import JsonData +from infrastructure.json_loan_repository import JsonLoanRepository +from infrastructure.json_patron_repository import JsonPatronRepository +from console.console_app import ConsoleApp + + +def main(): + json_data = JsonData() + patron_repo = JsonPatronRepository(json_data) + loan_repo = JsonLoanRepository(json_data) + loan_service = LoanService(loan_repo) + patron_service = PatronService(patron_repo) + app = ConsoleApp(loan_service, patron_service, patron_repo, loan_repo) + app.run() + +if __name__ == "__main__": + main() diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Authors.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Authors.json new file mode 100644 index 0000000..2f61038 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Authors.json @@ -0,0 +1,22 @@ +[ + {"Id": 1, "Name": "Author One"}, + {"Id": 2, "Name": "Author Two"}, + {"Id": 3, "Name": "Author Three"}, + {"Id": 4, "Name": "Author Four"}, + {"Id": 5, "Name": "Author Five"}, + {"Id": 6, "Name": "Author Six"}, + {"Id": 7, "Name": "Author Seven"}, + {"Id": 8, "Name": "Author Eight"}, + {"Id": 9, "Name": "Author Nine"}, + {"Id": 10, "Name": "Author Ten"}, + {"Id": 11, "Name": "Author Eleven"}, + {"Id": 12, "Name": "Author Twelve"}, + {"Id": 13, "Name": "Author Thirteen"}, + {"Id": 14, "Name": "Author Fourteen"}, + {"Id": 15, "Name": "Author Fifteen"}, + {"Id": 16, "Name": "Author Sixteen"}, + {"Id": 17, "Name": "Author Seventeen"}, + {"Id": 18, "Name": "Author Eighteen"}, + {"Id": 19, "Name": "Author Nineteen"}, + {"Id": 20, "Name": "Author Twenty"} +] diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/BookItems.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/BookItems.json new file mode 100644 index 0000000..f5e1d1b --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/BookItems.json @@ -0,0 +1,22 @@ +[ + {"Id": 1, "BookId": 1, "AcquisitionDate": "2023-09-20T00:40:43.1716563", "Condition": "Good"}, + {"Id": 2, "BookId": 2, "AcquisitionDate": "2023-09-20T00:40:43.1717503", "Condition": "Fair"}, + {"Id": 3, "BookId": 3, "AcquisitionDate": "2023-09-20T00:40:43.1717511", "Condition": "Excellent"}, + {"Id": 4, "BookId": 4, "AcquisitionDate": "2023-09-20T00:40:43.1717513", "Condition": "Poor"}, + {"Id": 5, "BookId": 5, "AcquisitionDate": "2023-09-20T00:40:43.1717516", "Condition": "Good"}, + {"Id": 6, "BookId": 6, "AcquisitionDate": "2023-09-20T00:40:43.1717521", "Condition": "Fair"}, + {"Id": 7, "BookId": 7, "AcquisitionDate": "2023-09-20T00:40:43.1717523", "Condition": "Excellent"}, + {"Id": 8, "BookId": 8, "AcquisitionDate": "2023-09-20T00:40:43.1717526", "Condition": "Poor"}, + {"Id": 9, "BookId": 9, "AcquisitionDate": "2023-09-20T00:40:43.171757", "Condition": "Good"}, + {"Id": 10, "BookId": 10, "AcquisitionDate": "2023-09-20T00:40:43.1717574", "Condition": "Fair"}, + {"Id": 11, "BookId": 11, "AcquisitionDate": "2023-09-20T00:40:43.1717576", "Condition": "Excellent"}, + {"Id": 12, "BookId": 12, "AcquisitionDate": "2023-09-20T00:40:43.1717578", "Condition": "Poor"}, + {"Id": 13, "BookId": 13, "AcquisitionDate": "2023-09-20T00:40:43.171758", "Condition": "Good"}, + {"Id": 14, "BookId": 14, "AcquisitionDate": "2023-09-20T00:40:43.1717609", "Condition": "Fair"}, + {"Id": 15, "BookId": 15, "AcquisitionDate": "2023-09-20T00:40:43.1717611", "Condition": "Excellent"}, + {"Id": 16, "BookId": 16, "AcquisitionDate": "2023-09-20T00:40:43.1717613", "Condition": "Poor"}, + {"Id": 17, "BookId": 17, "AcquisitionDate": "2023-09-20T00:40:43.1717616", "Condition": "Good"}, + {"Id": 18, "BookId": 18, "AcquisitionDate": "2023-09-20T00:40:43.1717619", "Condition": "Fair"}, + {"Id": 19, "BookId": 19, "AcquisitionDate": "2023-09-20T00:40:43.1717621", "Condition": "Excellent"}, + {"Id": 20, "BookId": 20, "AcquisitionDate": "2023-09-20T00:40:43.1717626", "Condition": "Poor"} +] diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Books.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Books.json new file mode 100644 index 0000000..ac80673 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Books.json @@ -0,0 +1,22 @@ +[ + {"Id": 1, "Title": "Book One", "AuthorId": 1, "Genre": "Dystopian", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524935"}, + {"Id": 2, "Title": "Book Two", "AuthorId": 2, "Genre": "Classic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524936"}, + {"Id": 3, "Title": "Book Three", "AuthorId": 3, "Genre": "Romance", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524937"}, + {"Id": 4, "Title": "Book Four", "AuthorId": 4, "Genre": "Classic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524938"}, + {"Id": 5, "Title": "Book Five", "AuthorId": 5, "Genre": "Coming-of-age", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524939"}, + {"Id": 6, "Title": "Book Six", "AuthorId": 6, "Genre": "Modernist", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524940"}, + {"Id": 7, "Title": "Book Seven", "AuthorId": 7, "Genre": "Adventure", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524941"}, + {"Id": 8, "Title": "Book Eight", "AuthorId": 8, "Genre": "Fantasy", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524942"}, + {"Id": 9, "Title": "Book Nine", "AuthorId": 9, "Genre": "Fantasy", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524943"}, + {"Id": 10, "Title": "Book Ten", "AuthorId": 10, "Genre": "Epic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524944"}, + {"Id": 11, "Title": "Book Eleven", "AuthorId": 11, "Genre": "Psychological", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524945"}, + {"Id": 12, "Title": "Book Twelve", "AuthorId": 12, "Genre": "Psychological", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524946"}, + {"Id": 13, "Title": "Book Thirteen", "AuthorId": 13, "Genre": "Magical realism", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524947"}, + {"Id": 14, "Title": "Book Fourteen", "AuthorId": 14, "Genre": "Classic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524948"}, + {"Id": 15, "Title": "Book Fifteen", "AuthorId": 15, "Genre": "Adventure", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524949"}, + {"Id": 16, "Title": "Book Sixteen", "AuthorId": 16, "Genre": "Historical", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524950"}, + {"Id": 17, "Title": "Book Seventeen", "AuthorId": 17, "Genre": "Gothic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524951"}, + {"Id": 18, "Title": "Book Eighteen", "AuthorId": 18, "Genre": "Dystopian", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524952"}, + {"Id": 19, "Title": "Book Nineteen", "AuthorId": 19, "Genre": "Classic", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524953"}, + {"Id": 20, "Title": "Book Twenty", "AuthorId": 20, "Genre": "Adventure", "ImageName": "BookCover01.jpg", "ISBN": "978-0451524954"} +] diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json new file mode 100644 index 0000000..8e56118 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json @@ -0,0 +1,402 @@ +[ + { + "Id": 1, + "BookItemId": 17, + "PatronId": 22, + "LoanDate": "2023-12-08T00:40:43.180886", + "DueDate": "2023-12-22T00:40:43.180886", + "ReturnDate": null + }, + { + "Id": 2, + "BookItemId": 6, + "PatronId": 28, + "LoanDate": "2023-12-17T00:40:43.180924", + "DueDate": "2023-12-31T00:40:43.180924", + "ReturnDate": null + }, + { + "Id": 3, + "BookItemId": 16, + "PatronId": 4, + "LoanDate": "2023-12-23T00:40:43.180928", + "DueDate": "2024-01-06T00:40:43.180928", + "ReturnDate": null + }, + { + "Id": 4, + "BookItemId": 17, + "PatronId": 14, + "LoanDate": "2023-12-22T00:40:43.180929", + "DueDate": "2024-01-05T00:40:43.180929", + "ReturnDate": null + }, + { + "Id": 5, + "BookItemId": 6, + "PatronId": 9, + "LoanDate": "2023-12-09T00:40:43.180929", + "DueDate": "2023-12-23T00:40:43.180929", + "ReturnDate": null + }, + { + "Id": 6, + "BookItemId": 14, + "PatronId": 25, + "LoanDate": "2023-12-27T00:40:43.180930", + "DueDate": "2024-01-10T00:40:43.180930", + "ReturnDate": null + }, + { + "Id": 7, + "BookItemId": 12, + "PatronId": 50, + "LoanDate": "2023-12-27T00:40:43.180930", + "DueDate": "2024-01-10T00:40:43.180930", + "ReturnDate": null + }, + { + "Id": 8, + "BookItemId": 18, + "PatronId": 28, + "LoanDate": "2023-12-26T00:40:43.180930", + "DueDate": "2024-01-09T00:40:43.180930", + "ReturnDate": null + }, + { + "Id": 9, + "BookItemId": 8, + "PatronId": 9, + "LoanDate": "2023-12-10T00:40:43.180930", + "DueDate": "2023-12-24T00:40:43.180930", + "ReturnDate": null + }, + { + "Id": 10, + "BookItemId": 16, + "PatronId": 3, + "LoanDate": "2023-12-26T00:40:43.180931", + "DueDate": "2024-01-09T00:40:43.180931", + "ReturnDate": null + }, + { + "Id": 11, + "BookItemId": 4, + "PatronId": 42, + "LoanDate": "2023-12-15T00:40:43.180931", + "DueDate": "2023-12-29T00:40:43.180931", + "ReturnDate": null + }, + { + "Id": 12, + "BookItemId": 17, + "PatronId": 7, + "LoanDate": "2023-12-23T00:40:43.180933", + "DueDate": "2024-01-06T00:40:43.180933", + "ReturnDate": null + }, + { + "Id": 13, + "BookItemId": 12, + "PatronId": 5, + "LoanDate": "2023-12-27T00:40:43.180933", + "DueDate": "2024-01-10T00:40:43.180933", + "ReturnDate": null + }, + { + "Id": 14, + "BookItemId": 4, + "PatronId": 9, + "LoanDate": "2023-12-10T00:40:43.180933", + "DueDate": "2023-12-24T00:40:43.180933", + "ReturnDate": null + }, + { + "Id": 15, + "BookItemId": 7, + "PatronId": 28, + "LoanDate": "2023-12-23T00:40:43.180933", + "DueDate": "2024-01-06T00:40:43.180933", + "ReturnDate": null + }, + { + "Id": 16, + "BookItemId": 14, + "PatronId": 3, + "LoanDate": "2023-12-08T00:40:43.180934", + "DueDate": "2023-12-22T00:40:43.180934", + "ReturnDate": null + }, + { + "Id": 17, + "BookItemId": 5, + "PatronId": 48, + "LoanDate": "2023-12-16T00:40:43.180934", + "DueDate": "2023-12-30T00:40:43.180934", + "ReturnDate": null + }, + { + "Id": 18, + "BookItemId": 4, + "PatronId": 49, + "LoanDate": "2023-12-19T00:40:43.180934", + "DueDate": "2024-01-02T00:40:43.180934", + "ReturnDate": null + }, + { + "Id": 19, + "BookItemId": 13, + "PatronId": 33, + "LoanDate": "2023-12-28T00:40:43.180935", + "DueDate": "2024-01-11T00:40:43.180935", + "ReturnDate": null + }, + { + "Id": 20, + "BookItemId": 14, + "PatronId": 48, + "LoanDate": "2023-12-27T00:40:43.180935", + "DueDate": "2024-01-10T00:40:43.180935", + "ReturnDate": null + }, + { + "Id": 21, + "BookItemId": 15, + "PatronId": 29, + "LoanDate": "2023-12-29T00:40:43.180935", + "DueDate": "2024-01-12T00:40:43.180935", + "ReturnDate": null + }, + { + "Id": 22, + "BookItemId": 7, + "PatronId": 30, + "LoanDate": "2023-12-30T00:40:43.180935", + "DueDate": "2024-01-13T00:40:43.180935", + "ReturnDate": null + }, + { + "Id": 23, + "BookItemId": 18, + "PatronId": 31, + "LoanDate": "2023-12-31T00:40:43.180936", + "DueDate": "2024-01-14T00:40:43.180936", + "ReturnDate": null + }, + { + "Id": 24, + "BookItemId": 8, + "PatronId": 32, + "LoanDate": "2023-12-29T00:40:43.180936", + "DueDate": "2024-01-12T00:40:43.180936", + "ReturnDate": null + }, + { + "Id": 25, + "BookItemId": 16, + "PatronId": 33, + "LoanDate": "2023-12-30T00:40:43.180936", + "DueDate": "2024-01-13T00:40:43.180936", + "ReturnDate": null + }, + { + "Id": 26, + "BookItemId": 5, + "PatronId": 34, + "LoanDate": "2023-12-31T00:40:43.180936", + "DueDate": "2024-01-14T00:40:43.180936", + "ReturnDate": null + }, + { + "Id": 27, + "BookItemId": 15, + "PatronId": 35, + "LoanDate": "2023-12-30T00:40:43.180937", + "DueDate": "2024-01-13T00:40:43.180937", + "ReturnDate": null + }, + { + "Id": 28, + "BookItemId": 7, + "PatronId": 36, + "LoanDate": "2023-12-31T00:40:43.180937", + "DueDate": "2024-01-14T00:40:43.180937", + "ReturnDate": null + }, + { + "Id": 29, + "BookItemId": 18, + "PatronId": 37, + "LoanDate": "2023-12-29T00:40:43.180937", + "DueDate": "2024-01-12T00:40:43.180937", + "ReturnDate": null + }, + { + "Id": 30, + "BookItemId": 8, + "PatronId": 38, + "LoanDate": "2023-12-30T00:40:43.180937", + "DueDate": "2024-01-13T00:40:43.180937", + "ReturnDate": null + }, + { + "Id": 31, + "BookItemId": 16, + "PatronId": 39, + "LoanDate": "2023-12-31T00:40:43.180938", + "DueDate": "2024-01-14T00:40:43.180938", + "ReturnDate": null + }, + { + "Id": 32, + "BookItemId": 5, + "PatronId": 40, + "LoanDate": "2023-12-29T00:40:43.180938", + "DueDate": "2024-01-12T00:40:43.180938", + "ReturnDate": null + }, + { + "Id": 33, + "BookItemId": 15, + "PatronId": 41, + "LoanDate": "2023-12-30T00:40:43.180938", + "DueDate": "2024-01-13T00:40:43.180938", + "ReturnDate": null + }, + { + "Id": 34, + "BookItemId": 7, + "PatronId": 42, + "LoanDate": "2023-12-31T00:40:43.180938", + "DueDate": "2024-01-14T00:40:43.180938", + "ReturnDate": "2025-06-04T15:40:27.824911" + }, + { + "Id": 35, + "BookItemId": 18, + "PatronId": 43, + "LoanDate": "2023-12-29T00:40:43.180939", + "DueDate": "2024-01-12T00:40:43.180939", + "ReturnDate": null + }, + { + "Id": 36, + "BookItemId": 8, + "PatronId": 44, + "LoanDate": "2023-12-30T00:40:43.180939", + "DueDate": "2024-01-13T00:40:43.180939", + "ReturnDate": null + }, + { + "Id": 37, + "BookItemId": 16, + "PatronId": 45, + "LoanDate": "2023-12-31T00:40:43.180939", + "DueDate": "2024-01-14T00:40:43.180939", + "ReturnDate": null + }, + { + "Id": 38, + "BookItemId": 5, + "PatronId": 46, + "LoanDate": "2023-12-29T00:40:43.180940", + "DueDate": "2024-01-12T00:40:43.180940", + "ReturnDate": null + }, + { + "Id": 39, + "BookItemId": 15, + "PatronId": 47, + "LoanDate": "2023-12-30T00:40:43.180940", + "DueDate": "2024-01-13T00:40:43.180940", + "ReturnDate": null + }, + { + "Id": 40, + "BookItemId": 7, + "PatronId": 48, + "LoanDate": "2023-12-31T00:40:43.180940", + "DueDate": "2024-01-14T00:40:43.180940", + "ReturnDate": null + }, + { + "Id": 41, + "BookItemId": 18, + "PatronId": 49, + "LoanDate": "2023-12-29T00:40:43.180940", + "DueDate": "2024-01-12T00:40:43.180940", + "ReturnDate": null + }, + { + "Id": 42, + "BookItemId": 8, + "PatronId": 50, + "LoanDate": "2023-12-30T00:40:43.180941", + "DueDate": "2024-01-13T00:40:43.180941", + "ReturnDate": null + }, + { + "Id": 43, + "BookItemId": 16, + "PatronId": 1, + "LoanDate": "2023-12-31T00:40:43.180941", + "DueDate": "2024-01-14T00:40:43.180941", + "ReturnDate": "2025-06-04T12:09:31.031029" + }, + { + "Id": 44, + "BookItemId": 5, + "PatronId": 2, + "LoanDate": "2023-12-29T00:40:43.180941", + "DueDate": "2024-01-12T00:40:43.180941", + "ReturnDate": null + }, + { + "Id": 45, + "BookItemId": 15, + "PatronId": 3, + "LoanDate": "2023-12-30T00:40:43.180942", + "DueDate": "2024-01-13T00:40:43.180942", + "ReturnDate": "2025-06-04T15:10:11.703073" + }, + { + "Id": 46, + "BookItemId": 7, + "PatronId": 4, + "LoanDate": "2023-12-31T00:40:43.180942", + "DueDate": "2024-01-14T00:40:43.180942", + "ReturnDate": null + }, + { + "Id": 47, + "BookItemId": 18, + "PatronId": 5, + "LoanDate": "2023-12-29T00:40:43.180942", + "DueDate": "2024-01-12T00:40:43.180942", + "ReturnDate": null + }, + { + "Id": 48, + "BookItemId": 8, + "PatronId": 6, + "LoanDate": "2023-12-30T00:40:43.180942", + "DueDate": "2024-01-13T00:40:43.180942", + "ReturnDate": null + }, + { + "Id": 49, + "BookItemId": 16, + "PatronId": 7, + "LoanDate": "2023-12-31T00:40:43.180943", + "DueDate": "2024-01-14T00:40:43.180943", + "ReturnDate": null + }, + { + "Id": 50, + "BookItemId": 5, + "PatronId": 8, + "LoanDate": "2023-12-29T00:40:43.180943", + "DueDate": "2024-01-12T00:40:43.180943", + "ReturnDate": null + } +] \ No newline at end of file diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Patrons.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Patrons.json new file mode 100644 index 0000000..7c05687 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Patrons.json @@ -0,0 +1,52 @@ +[ + {"Id": 1, "Name": "Patron One", "MembershipEnd": "2024-12-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron One.jpg"}, + {"Id": 2, "Name": "Patron Two", "MembershipEnd": "2025-02-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Two.jpg"}, + {"Id": 3, "Name": "Patron Three", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Three.jpg"}, + {"Id": 4, "Name": "Patron Four", "MembershipEnd": "2025-04-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Four.jpg"}, + {"Id": 5, "Name": "Patron Five", "MembershipEnd": "2025-05-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Five.jpg"}, + {"Id": 6, "Name": "Patron Six", "MembershipEnd": "2025-06-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Six.jpg"}, + {"Id": 7, "Name": "Patron Seven", "MembershipEnd": "2025-07-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Seven.jpg"}, + {"Id": 8, "Name": "Patron Eight", "MembershipEnd": "2024-01-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Eight.jpg"}, + {"Id": 9, "Name": "Patron Nine", "MembershipEnd": "2024-02-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Nine.jpg"}, + {"Id": 10, "Name": "Patron Ten", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Ten.jpg"}, + {"Id": 11, "Name": "Patron Eleven", "MembershipEnd": "2024-04-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Eleven.jpg"}, + {"Id": 12, "Name": "Patron Twelve", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twelve.jpg"}, + {"Id": 13, "Name": "Patron Thirteen", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirteen.jpg"}, + {"Id": 14, "Name": "Patron Fourteen", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Fourteen.jpg"}, + {"Id": 15, "Name": "Patron Fifteen", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Fifteen.jpg"}, + {"Id": 16, "Name": "Patron Sixteen", "MembershipEnd": "2024-04-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Sixteen.jpg"}, + {"Id": 17, "Name": "Patron Seventeen", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Seventeen.jpg"}, + {"Id": 18, "Name": "Patron Eighteen", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Eighteen.jpg"}, + {"Id": 19, "Name": "Patron Nineteen", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Nineteen.jpg"}, + {"Id": 20, "Name": "Patron Twenty", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty.jpg"}, + {"Id": 21, "Name": "Patron Twenty-One", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-One.jpg"}, + {"Id": 22, "Name": "Patron Twenty-Two", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Two.jpg"}, + {"Id": 23, "Name": "Patron Twenty-Three", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Three.jpg"}, + {"Id": 24, "Name": "Patron Twenty-Four", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Four.jpg"}, + {"Id": 25, "Name": "Patron Twenty-Five", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Five.jpg"}, + {"Id": 26, "Name": "Patron Twenty-Six", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Six.jpg"}, + {"Id": 27, "Name": "Patron Twenty-Seven", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Seven.jpg"}, + {"Id": 28, "Name": "Patron Twenty-Eight", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Eight.jpg"}, + {"Id": 29, "Name": "Patron Twenty-Nine", "MembershipEnd": "2024-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Twenty-Nine.jpg"}, + {"Id": 30, "Name": "Patron Thirty", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty.jpg"}, + {"Id": 31, "Name": "Patron Thirty-One", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-One.jpg"}, + {"Id": 32, "Name": "Patron Thirty-Two", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Two.jpg"}, + {"Id": 33, "Name": "Patron Thirty-Three", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Three.jpg"}, + {"Id": 34, "Name": "Patron Thirty-Four", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Four.jpg"}, + {"Id": 35, "Name": "Patron Thirty-Five", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Five.jpg"}, + {"Id": 36, "Name": "Patron Thirty-Six", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Six.jpg"}, + {"Id": 37, "Name": "Patron Thirty-Seven", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Seven.jpg"}, + {"Id": 38, "Name": "Patron Thirty-Eight", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Eight.jpg"}, + {"Id": 39, "Name": "Patron Thirty-Nine", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Thirty-Nine.jpg"}, + {"Id": 40, "Name": "Patron Forty", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty.jpg"}, + {"Id": 41, "Name": "Patron Forty-One", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-One.jpg"}, + {"Id": 42, "Name": "Patron Forty-Two", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Two.jpg"}, + {"Id": 43, "Name": "Patron Forty-Three", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Three.jpg"}, + {"Id": 44, "Name": "Patron Forty-Four", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Four.jpg"}, + {"Id": 45, "Name": "Patron Forty-Five", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Five.jpg"}, + {"Id": 46, "Name": "Patron Forty-Six", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Six.jpg"}, + {"Id": 47, "Name": "Patron Forty-Seven", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Seven.jpg"}, + {"Id": 48, "Name": "Patron Forty-Eight", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Eight.jpg"}, + {"Id": 49, "Name": "Patron Forty-Nine", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Forty-Nine.jpg"}, + {"Id": 50, "Name": "Patron Fifty", "MembershipEnd": "2025-03-01T00:40:43.1589724", "MembershipStart": "2001-01-01T00:40:43.1589724", "ImageName": "Patron Fifty.jpg"} +] diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_data.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_data.py new file mode 100644 index 0000000..0c4aa54 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_data.py @@ -0,0 +1,105 @@ +import json +import os +from pathlib import Path +from application_core.entities.author import Author +from application_core.entities.book import Book +from application_core.entities.book_item import BookItem +from application_core.entities.patron import Patron +from application_core.entities.loan import Loan +from typing import List, Optional +from datetime import datetime + +class JsonData: + def __init__(self): + # Get the absolute path to the project root + self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + self.json_dir = os.path.join(self.project_root, "infrastructure", "Json") + self.authors_path = os.path.join(self.json_dir, "Authors.json") + self.books_path = os.path.join(self.json_dir, "Books.json") + self.book_items_path = os.path.join(self.json_dir, "BookItems.json") # <-- Add this line + self.patrons_path = os.path.join(self.json_dir, "Patrons.json") + self.loans_path = os.path.join(self.json_dir, "Loans.json") + self.authors: List[Author] = [] + self.books: List[Book] = [] + self.book_items: List[BookItem] = [] + self.patrons: List[Patron] = [] + self.loans: List[Loan] = [] + self._loaded = False + self.load_data() + + def _parse_datetime(self, value: Optional[str]) -> Optional[datetime]: + if value is None: + return None + return datetime.fromisoformat(value) + + def load_data(self) -> None: + try: + with open(self.authors_path, encoding='utf-8') as f: + authors_data = json.load(f) + self.authors = [Author(id=a['Id'], name=a['Name']) for a in authors_data] + with open(self.books_path, encoding='utf-8') as f: + books_data = json.load(f) + self.books = [Book(id=b['Id'], title=b['Title'], author_id=b['AuthorId'], genre=b['Genre'], image_name=b['ImageName'], isbn=b['ISBN']) for b in books_data] + with open(self.book_items_path, encoding='utf-8') as f: # <-- Fix here + items_data = json.load(f) + self.book_items = [BookItem(id=bi['Id'], book_id=bi['BookId'], acquisition_date=self._parse_datetime(bi['AcquisitionDate']), condition=bi.get('Condition')) for bi in items_data] + with open(self.patrons_path, encoding='utf-8') as f: + patrons_data = json.load(f) + self.patrons = [Patron(id=p['Id'], name=p['Name'], membership_end=self._parse_datetime(p['MembershipEnd']), membership_start=self._parse_datetime(p['MembershipStart']), image_name=p.get('ImageName')) for p in patrons_data] + with open(self.loans_path, encoding='utf-8') as f: + loans_data = json.load(f) + self.loans = [Loan(id=l['Id'], book_item_id=l['BookItemId'], patron_id=l['PatronId'], loan_date=self._parse_datetime(l['LoanDate']), due_date=self._parse_datetime(l['DueDate']), return_date=self._parse_datetime(l['ReturnDate'])) for l in loans_data] + self._loaded = True + + # Build lookup dictionaries for fast access + book_item_dict = {bi.id: bi for bi in self.book_items} + book_dict = {b.id: b for b in self.books} + author_dict = {a.id: a for a in self.authors} + patron_dict = {p.id: p for p in self.patrons} + + # Link book_item and book to each loan + for loan in self.loans: + loan.book_item = book_item_dict.get(loan.book_item_id) + if loan.book_item: + loan.book_item.book = book_dict.get(loan.book_item.book_id) + if loan.book_item.book: + loan.book_item.book.author = author_dict.get(loan.book_item.book.author_id) + loan.patron = patron_dict.get(loan.patron_id) + # Optionally, link loans to patrons + for patron in self.patrons: + patron.loans = [loan for loan in self.loans if loan.patron_id == patron.id] + + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error loading data: {e}") + self._loaded = False + + def save_loans(self, loans: List[Loan]) -> None: + try: + with open(self.loans_path, 'w', encoding='utf-8') as f: + json.dump([ + { + 'Id': l.id, + 'BookItemId': l.book_item_id, + 'PatronId': l.patron_id, + 'LoanDate': l.loan_date.isoformat() if l.loan_date else None, + 'DueDate': l.due_date.isoformat() if l.due_date else None, + 'ReturnDate': l.return_date.isoformat() if l.return_date else None + } for l in loans + ], f, indent=2) + except Exception as e: + print(f"Error saving loans: {e}") + + def save_patrons(self, patrons: List[Patron]) -> None: + try: + with open(self.patrons_path, 'w', encoding='utf-8') as f: + json.dump([ + { + 'Id': p.id, + 'Name': p.name, + 'MembershipEnd': p.membership_end.isoformat() if p.membership_end else None, + 'MembershipStart': p.membership_start.isoformat() if p.membership_start else None, + 'ImageName': p.image_name + } for p in patrons + ], f, indent=2) + except Exception as e: + print(f"Error saving patrons: {e}") diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_loan_repository.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_loan_repository.py new file mode 100644 index 0000000..7773f22 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_loan_repository.py @@ -0,0 +1,24 @@ +from application_core.interfaces.iloan_repository import ILoanRepository +from application_core.entities.loan import Loan +from .json_data import JsonData +from typing import Optional + +class JsonLoanRepository(ILoanRepository): + def __init__(self, json_data: JsonData): + self._json_data = json_data + + def get_loan(self, loan_id: int) -> Optional[Loan]: + for loan in self._json_data.loans: + if loan.id == loan_id: + return loan + return None + + def update_loan(self, loan: Loan) -> None: + for idx, l in enumerate(self._json_data.loans): + if l.id == loan.id: + self._json_data.loans[idx] = loan + self._json_data.save_loans(self._json_data.loans) + return + + def get_loans_by_patron_id(self, patron_id: int): + return [loan for loan in self._json_data.loans if loan.patron_id == patron_id] diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_patron_repository.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_patron_repository.py new file mode 100644 index 0000000..e9d6e98 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/json_patron_repository.py @@ -0,0 +1,26 @@ +from application_core.interfaces.ipatron_repository import IPatronRepository +from application_core.entities.patron import Patron +from .json_data import JsonData +from typing import List, Optional + +class JsonPatronRepository(IPatronRepository): + def __init__(self, json_data: JsonData): + self._json_data = json_data + + def get_patron(self, patron_id: int) -> Optional[Patron]: + for patron in self._json_data.patrons: + if patron.id == patron_id: + return patron + return None + + def search_patrons(self, search_input: str) -> List[Patron]: + results = [p for p in self._json_data.patrons if search_input.lower() in p.name.lower()] + results.sort(key=lambda p: p.name) + return results + + def update_patron(self, patron: Patron) -> None: + for idx, p in enumerate(self._json_data.patrons): + if p.id == patron.id: + self._json_data.patrons[idx] = patron + self._json_data.save_patrons(self._json_data.patrons) + return diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_loan_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_loan_service.py new file mode 100644 index 0000000..d786b44 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_loan_service.py @@ -0,0 +1,32 @@ +import unittest +from unittest.mock import MagicMock +from application_core.services.loan_service import LoanService +from application_core.entities.loan import Loan +from application_core.entities.patron import Patron +from application_core.enums.loan_extension_status import LoanExtensionStatus +from application_core.enums.loan_return_status import LoanReturnStatus +from datetime import datetime, timedelta + +class LoanServiceTest(unittest.TestCase): + def setUp(self): + self.mock_repo = MagicMock() + self.service = LoanService(self.mock_repo) + + def test_extend_loan_success(self): + print("Running test_extend_loan_success...") + patron = Patron(id=1, name="John Doe", membership_end=datetime.now()+timedelta(days=1), membership_start=datetime.now()-timedelta(days=30)) + loan = Loan(id=1, book_item_id=1, patron_id=1, patron=patron, loan_date=datetime.now()-timedelta(days=2), due_date=datetime.now()+timedelta(days=1)) + self.mock_repo.get_loan.return_value = loan + status = self.service.extend_loan(1) + print(f"extend_loan status: {status}") + self.assertEqual(status, LoanExtensionStatus.SUCCESS) + + def test_return_loan_not_found(self): + print("Running test_return_loan_not_found...") + self.mock_repo.get_loan.return_value = None + status = self.service.return_loan(1) + print(f"return_loan status: {status}") + self.assertEqual(status, LoanReturnStatus.LOAN_NOT_FOUND) + +if __name__ == "__main__": + unittest.main() diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_patron_service.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_patron_service.py new file mode 100644 index 0000000..a8a5ba2 --- /dev/null +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/tests/test_patron_service.py @@ -0,0 +1,30 @@ +import unittest +from unittest.mock import MagicMock +from application_core.services.patron_service import PatronService +from application_core.entities.patron import Patron +from application_core.enums.membership_renewal_status import MembershipRenewalStatus +from datetime import datetime, timedelta + +class PatronServiceTest(unittest.TestCase): + def setUp(self): + self.mock_repo = MagicMock() + self.service = PatronService(self.mock_repo) + + def test_renew_membership_success(self): + print("Running test_renew_membership_success...") + patron = Patron(id=1, name="John Doe", membership_end=datetime.now()-timedelta(days=1), membership_start=datetime.now()-timedelta(days=365)) + self.mock_repo.get_patron.return_value = patron + self.mock_repo.update_patron.return_value = None + status = self.service.renew_membership(1) + print(f"renew_membership status: {status}") + self.assertEqual(status, MembershipRenewalStatus.SUCCESS) + + def test_renew_membership_patron_not_found(self): + print("Running test_renew_membership_patron_not_found...") + self.mock_repo.get_patron.return_value = None + status = self.service.renew_membership(1) + print(f"renew_membership status: {status}") + self.assertEqual(status, MembershipRenewalStatus.PATRON_NOT_FOUND) + +if __name__ == "__main__": + unittest.main() From 78d609769400e5701b4ef9f886bc66661ba2dd1a Mon Sep 17 00:00:00 2001 From: Eric Camplin Date: Wed, 4 Jun 2025 16:11:15 -0700 Subject: [PATCH 2/4] M2 updated Lab instructions - working --- .../LAB_AK_02_analyze_document_code_py.md | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 Instructions/Labs/LAB_AK_02_analyze_document_code_py.md diff --git a/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md b/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md new file mode 100644 index 0000000..b95723d --- /dev/null +++ b/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md @@ -0,0 +1,332 @@ +--- +lab: + title: 'Exercise - Analyze and document code using GitHub Copilot' + description: 'Learn how to analyze new or unfamiliar code and how to generate documentation using GitHub Copilot in Visual Studio Code.' +--- + +# Analyze and document code using GitHub Copilot + +GitHub Copilot can help you understand and document a codebase by generating explanations and documentation. In this exercise, you use GitHub Copilot to analyze a codebase and generate documentation for the project. + +This exercise should take approximately **20** minutes to complete. + +> **IMPORTANT**: To complete this exercise, you must provide your own GitHub account and GitHub Copilot subscription. If you don't have a GitHub account, you can sign up for a free individual account and use a GitHub Copilot Free plan to complete the exercise. If you have access to a GitHub Copilot Pro, GitHub Copilot Pro+, GitHub Copilot Business, or GitHub Copilot Enterprise subscription from within your lab environment, you can use your existing GitHub Copilot subscription to complete this exercise. + +## Before you start + +Your lab environment must include the following: Git 2.48 or later, Python 3.8 or later, Visual Studio Code with the Python extension, and access to a GitHub account with GitHub Copilot enabled. + +If you're using a local PC as a lab environment for this exercise: + +- For help configuring your local PC as your lab environment, open the following link in a browser: Configure your lab environment resources. + +- For help enabling your GitHub Copilot subscription in Visual Studio Code, open the following link in a browser: Enable GitHub Copilot within Visual Studio Code. + +If you're using a hosted lab environment for this exercise: + +- For help enabling your GitHub Copilot subscription in Visual Studio Code, paste the following URL into a browser's site navigation bar: Enable GitHub Copilot within Visual Studio Code. + +- Open a command terminal and then run the following commands: + + To ensure that Visual Studio Code is configured to use the correct version of Python, verify your Python installation is version 3.1 or later: + + ```bash + python --version + ``` + +## Exercise scenario + +You're a developer working in the IT department of your local community. The backend systems that support the public library were lost in a fire. Your team needs to develop a temporary solution to help the library staff manage their operations until the system can be replaced. Your team chose GitHub Copilot to accelerate the development process. + +Your colleague has developed an initial version of the library application, but due to time constraints, they haven't had a chance to document the code. You need to analyze the codebase and create documentation for the project. + +This exercise includes the following tasks: + +- Set up the library application in Visual Studio Code. +- Use GitHub Copilot to explain the library application codebase. +- Use GitHub Copilot to create a README.md file for the library application. + +## Set up the library application in Visual Studio Code + +Your colleague has developed an initial version of the library application and has made it available as a .zip file. You need to download the zip file, extract the code files, and then open the solution in Visual Studio Code. + +Use the following steps to set up the library application: + +1. Open a browser window in your lab environment. + +1. To download a zip file containing the library application, paste the following URL into your browser's address bar: [GitHub Copilot lab - Analyze and document code](https://github.com/MicrosoftLearning/mslearn-github-copilot-dev/raw/refs/heads/main/DownloadableCodeProjects/Downloads/AZ2007LabAppM2.zip) + + The zip file named AZ2007LabAppM2.zip will be downloaded to your lab environment. + +1. Extract the files from the **AZ2007LabAppM2.zip** file. + + For example: + + 1. Navigate to the downloads folder in your lab environment. + + 1. Right-click **AZ2007LabAppM2.zip**, and then select **Extract all**. + + 1. Select **Show extracted files when complete**, and then select **Extract**. + +1. Open the extracted files folder, then copy the **AccelerateDevGHCopilotPython** folder to a location that's easy to access, such as your Windows Desktop folder. + +1. Open the **AccelerateDevGHCopilotPython** folder in Visual Studio Code. + + For example: + + 1. Open Visual Studio Code in your lab environment. + + 1. In Visual Studio Code, on the **File** menu, select **Open Folder**. + + 1. Navigate to the Windows Desktop folder, select **AccelerateDevGHCopilotPython** and then select **Select Folder**. + +1. In the Visual Studio Code EXPLORER view, expand the folder to show the following structure: + + - AccelerateDevGHCopilotPython/library + ├── application_core + ├── console + ├── infrastructure + └── tests + +1. Ensure that the application runs successfully. + + For example, open a terminal in Visual Studio Code, navigate to the **AccelerateDevGHCopilotPython/library** directory, and run the following command: + + ```bash + python -m unittest discover tests + ``` + + You'll see some Warnings, but there shouldn't be any Errors. + +## Use GitHub Copilot to explain the library application codebase + +GitHub Copilot can help you to understand an unfamiliar codebase by generating explanations at the solution, file, and code line levels. + +### Analyze code using prompts in the Chat view + +GitHub Copilot's Chat view includes a chat-based interface that allows you to interact with GitHub Copilot using natural language prompts. When evaluating an existing codebase for the first time, you can create prompts that generate an explanation at the workspace or project level, or at the code block or code line level. To assist you in specifying the context of your prompt, GitHub Copilot provides chat participants, chat variables, and slash commands. + +- Chat participants are used to scope your prompt to a specific domain. +- Chat variables are used to include specific context in your prompt. +- Slash commands are used to avoid writing complex prompts for common scenarios. + +Use the following steps to complete this section of the exercise: + +1. Ensure that the AccelerateDevGHCopilotPython/library solution is open in Visual Studio Code. + +1. Open GitHub Copilot's Chat view. + + To open the Chat view, select the **Toggle Chat** button at the top of the Visual Studio Code window. + + ![Screenshot showing the GitHub Copilot status menu.](./Media/m02-github-copilot-toggle-chat.png) + + You can also open the Chat view using the **Ctrl+Alt+I** keyboard shortcut. + +1. In the Chat view, enter a prompt that uses GitHub Copilot's **@workspace** Chat participant to generate a description of the project. + + For example, enter the following prompt in the Chat view: + + ```plaintext + @workspace describe this project + ``` + + Use Chat participants, such as the **@workspace**, to improve the responses generated by GitHub Copilot. Chat participants work like domain experts who can help you in their specialized areas. Use **@workspace** when you want GitHub Copilot to consider the structure of your project, how different parts of your code interact, or design patterns in your project. + + To see a list of all available chat participants, type **@** in the chat prompt box. + +1. Take a minute to compare GitHub Copilot's response with the actual project files. + + You should see a response that describes each of the projects in the solution: + + - **application_core** + - **infrastructure** + - **console** + - **tests** + +1. Use the EXPLORER view to expand the project folders. + +1. Locate and then open the **console_app.py** file. + + The **console_app.py** file is located in the **application _core\console** folder. + +1. Take a moment to review the code file. + +1. Enter a prompt in the Chat view that generates a description of the **console_app.py** class. + + For example, enter the following prompt in the Chat view: + + ```plaintext + @workspace #usages How is the ConsoleApp class used? + ``` + + Use chat variables, such as **#usages**, to include specific context in your prompt. To see a list of the chat variables, type **#** in the chat prompt box. + + > **NOTE**: GitHub Copilot considers your chat history and the code files you have open in Visual Studio Code when constructing a context for your prompt and generating a response. + +1. Take a minute to verify the accuracy of GitHub Copilot's response. + + You should see a response that the describes where the **ConsoleApp** class is defined and how it's used in the codebase. The **console_app.py** and **main.py** files are referenced in the response, along with line numbers + +1. Open the **main.py** file from the root of the **application _core\console** folder and examine the code. + +1. Enter a prompt in the Chat view that generates an explanation of the **main.py** file. + + For example, enter the following prompt in the Chat view: + + ```plaintext + @workspace /explain Explain the main.py file + ``` + + Use Slash commands, such as **/explain**, to avoid writing complex prompts for common scenarios. To see a list of all available slash commands, type **/** in the chat prompt box. Available slash commands may vary, depending on your environment and the context of your chat. + +1. Take a minute to review the detailed response generated by GitHub Copilot. + + You should see a response that includes an overview and a breakdown that explains how the file is used within the application. + +1. Close the **main.py** file. + +### Improve chat responses by adding context + +GitHub Copilot uses context to generate more relevant responses. + +Opening files in the code editor is one way to establish context, but you can also add files to the Chat context using drag-and-drop operations or by using the **Attach Context** button in the Chat view. + +Use the following steps to complete this section of the exercise: + +1. Expand the **Infrastructure** folder. + +1. Use a drag-and-drop operation to add the following files from the SOLUTION EXPLORER view to the Chat context: **json_data.py**, **json_loan_repository.py**, and **json_patron_repository.py**. + + GitHub Copilot uses the Chat Context to understand the code files that are relevant to your prompt. You can add files to the Chat context using drag-and-drop operations, or you can use the **Attach Context** button in the Chat view. + + Instead of adding individual files manually, you can let Copilot find the right files from your codebase automatically. This can be useful when you don't know which files are relevant to your question. + + To let Copilot find the right files automatically, add #codebase in your prompt or select Codebase from the list of context types. + +1. Enter a prompt in the Chat view that generates an explanation of the data access classes. + + For example, enter the following prompt in the Chat view: + + ```plaintext + @workspace /explain Explain how the data access classes work + ``` + +1. Take a couple minutes to read through the response. + + You should see a response that describes each of the data access classes (**json_data.py**, **json_loan_repository.py**, and **json_patron_repository.py**) and how they work together to manage data access in the application. Key methods, such as **load_data**, **save_loans**, and **save_patrons**, should be mentioned in the response. + +1. Take a minute to examine the JSON data files that are used to simulate library records. + + The JSON data files are located in the **infrastructure/json** folder. + + The data files use ID properties to link entities. For example, a **loan** object has a **patron_id** property that links to a **patron** object with the same ID. The JSON files contain data for authors, books, book items, patrons, and loans. + + > **NOTE**: Notice that Author names, book titles, and patron names have been anonymized for the purposes of this training. + +### Build and run the application + +Running the application helps you understand the user interface, key features of the application, and how app components interact. + +Use the following steps to complete this section of the exercise: + +1. Ensure that you have the **Explorer** view open. + + The Solution Explorer view is not the same as the Explorer view. The Solution Explorer view uses project and solution files as "directory" nodes to display the structure of the solution. + +1. To run the application, open **console/main.py** so it desplays in the editor window, select the key comination **CTRL+Shift+D**, and then select **Python Debugger** if needed and start debugging (**F5**). + + The following steps guide you through a simple use case. + +1. When prompted for a patron name, type **One** and then press Enter. + + You should see a list of patrons that match the search query. + + > **NOTE**: The application uses a case-sensitive search process. + +1. At the "Input Options" prompt, type **2** and then press Enter. + + Entering **2** selects the second patron in the list. + + You should see the patron's name and membership status followed by book loan details. + +1. At the "Input Options" prompt, type **1** and then press Enter. + + Entering **1** selects the first book in the list. + + You should see book details listed, including the due date and return status. + +1. At the "Input Options" prompt, type **r** and then press Enter. + + Entering **r** returns the book. + +1. Verify that the message "Book was successfully returned." is displayed. + + The message "Book was successfully returned." should be followed by the book details. Returned books are marked with **Returned: True**. + +1. To begin a new search, type **s** and then press Enter. + +1. When prompted for a patron name, type **One** and then press Enter. + +1. At the "Input Options" prompt, type **2** and then press Enter. + +1. Verify that first book loan is marked **Returned: True**. + +1. At the "Input Options" prompt, type **q** and then press Enter. + +1. Stop the debug session. + +## Create the project documentation for the README file + +Readme files provide project contributors and stakeholders with essential information about a code repository. They help users understand the purpose of the project, how to use it, and how to contribute. A well-structured README file can significantly improve the usability and maintainability of a project. + +You need a README file that includes the following sections: + +- **Project Title**: A brief, clear title for the project. +- **Description**: A detailed explanation of what the project is and what it does. +- **Project Structure**: A breakdown of the project structure, including key folders and files. +- **Key Classes and Interfaces**: A list of key classes and interfaces in the project. +- **Usage**: Instructions on how to use the project, often including code examples. +- **License**: The license that the project is under. + +In this section of the exercise, you'll use GitHub Copilot to create project documentation and add it to a **README.md** file. + +Use the following steps to complete this section of the exercise: + +1. Add a new file named **README.md** to the root folder of the **AccelerateDevGHCopilotPython** solution. + +1. Open the Chat view. + +1. To generate project documentation for your README file, enter the following prompt: + + ```plaintext + @workspace Generate the contents of a README.md file for a code repository. + Use "Library App" as the project title. The README file should include the + following sections: Description, Project Structure, Key Classes and Interfaces, + Usage, License. Format all sections as raw markdown. Use a bullet list with + indents to represent the project structure. Do not include ".gitignore", ". + pyc", or the ".github", "__pycache__" folders. + ``` + + > **NOTE**: Using multiple prompts, one for each section of the README file would produce more detailed results. A single prompt is used in this exercise to simplify the process. + +1. Review the response to ensure each section is formatted as markdown. + + You can update sections individually to provide more detailed information or if they aren't formatted correctly. You can also copy GitHub Copilot's response to the README file and then make corrections directly in the markdown file. + +1. Copy the suggested documentation, and then paste it into the README.md file. + + To copy the entire response, scroll to the bottom of the response, right-click in the empty space to the right of the "thumbs-up" icon, and then select **Copy** + +1. Format the README.md file as needed. + + You can update the settings for GitHub Copilot to enable markdown formatting, then use GitHub Copilot to help you update sections of the README.md file. + + When you're finished, you should have a README.md file that includes each of the specified sections, with an appropriate level of detail. + +## Summary + +In this exercise, you learned how to use GitHub Copilot to analyze and document a codebase. You used GitHub Copilot to generate explanations for the project structure, key classes, and data access classes. You also used GitHub Copilot to create a README.md file for the project. + +## Clean up + +Now that you've finished the exercise, take a minute to ensure that you haven't made changes to your GitHub account or GitHub Copilot subscription that you don't want to keep. If you made any changes, revert them now. From 2ba78f6170159f88c2f99d7c94f4975b1db74fb1 Mon Sep 17 00:00:00 2001 From: Chris Howd Date: Thu, 5 Jun 2025 13:53:59 -0700 Subject: [PATCH 3/4] minor edits --- .../library/infrastructure/Json/Loans.json | 2 +- .../LAB_AK_02_analyze_document_code_py.md | 38 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json index 8e56118..5ad9e27 100644 --- a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/infrastructure/Json/Loans.json @@ -341,7 +341,7 @@ "PatronId": 1, "LoanDate": "2023-12-31T00:40:43.180941", "DueDate": "2024-01-14T00:40:43.180941", - "ReturnDate": "2025-06-04T12:09:31.031029" + "ReturnDate": null }, { "Id": 44, diff --git a/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md b/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md index b95723d..e5b4dfb 100644 --- a/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md +++ b/Instructions/Labs/LAB_AK_02_analyze_document_code_py.md @@ -88,6 +88,10 @@ Use the following steps to set up the library application: ├── infrastructure └── tests + ## Use GitHub Copilot to explain the library application codebase @@ -147,7 +165,7 @@ Use the following steps to complete this section of the exercise: 1. Locate and then open the **console_app.py** file. - The **console_app.py** file is located in the **application _core\console** folder. + The **console_app.py** file is located in the **library\console** folder. 1. Take a moment to review the code file. @@ -167,7 +185,7 @@ Use the following steps to complete this section of the exercise: You should see a response that the describes where the **ConsoleApp** class is defined and how it's used in the codebase. The **console_app.py** and **main.py** files are referenced in the response, along with line numbers -1. Open the **main.py** file from the root of the **application _core\console** folder and examine the code. +1. Open the **main.py** file from the root of the **library\console** folder and examine the code. 1. Enter a prompt in the Chat view that generates an explanation of the **main.py** file. @@ -213,7 +231,7 @@ Use the following steps to complete this section of the exercise: 1. Take a couple minutes to read through the response. - You should see a response that describes each of the data access classes (**json_data.py**, **json_loan_repository.py**, and **json_patron_repository.py**) and how they work together to manage data access in the application. Key methods, such as **load_data**, **save_loans**, and **save_patrons**, should be mentioned in the response. + You should see a response that describes each of the data access classes (**json_data.py**, **json_loan_repository.py**, and **json_patron_repository.py**) and how they work together to manage data access in the application. Key methods, such as **save_patrons**, **get_patron**, and **get_loan** should be mentioned in the response. 1. Take a minute to examine the JSON data files that are used to simulate library records. @@ -231,11 +249,17 @@ Use the following steps to complete this section of the exercise: 1. Ensure that you have the **Explorer** view open. - The Solution Explorer view is not the same as the Explorer view. The Solution Explorer view uses project and solution files as "directory" nodes to display the structure of the solution. +1. Open **console/main.py** so it displays in the editor window. + +1. To run the application, use one of the following methods: + + - In the top menu, select **Run** > **Run Without Debugging**. + - At the top right of the editor, click the "Run Python File" button (▶️). + - Right-click in the editor, select **Run Python**, and then select **Run Python File in Terminal**. -1. To run the application, open **console/main.py** so it desplays in the editor window, select the key comination **CTRL+Shift+D**, and then select **Python Debugger** if needed and start debugging (**F5**). + The app will start in the integrated terminal, and you can interact with it there. - The following steps guide you through a simple use case. + > **NOTE**: The following steps guide you through a simple use case. 1. When prompted for a patron name, type **One** and then press Enter. From a13f77286dfef3176925f2ecc6fd44a90905f86e Mon Sep 17 00:00:00 2001 From: Eric Camplin Date: Fri, 6 Jun 2025 08:42:05 -0700 Subject: [PATCH 4/4] Update common_actions.py removed SEARCH_BOOKS this is added in the next exercise --- .../library/console/common_actions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py index 1d846e6..011eda9 100644 --- a/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py +++ b/DownloadableCodeProjects/az-2007-m2-explain-document/AccelerateDevGHCopilotPython/library/console/common_actions.py @@ -8,4 +8,3 @@ class CommonActions(Flag): RENEW_PATRON_MEMBERSHIP = auto() RETURN_LOANED_BOOK = auto() EXTEND_LOANED_BOOK = auto() - SEARCH_BOOKS = auto()