diff --git a/README.md b/README.md index dbcd01351cca650ec888ee561b73741453b83755..64347809c2e2a37f909bd2ae85a68c13588331fc 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,9 @@ airline-ticket-sales-service/ # Корневая директория РїСЂРѕРµ в”њв”Ђв”Ђ main.py # РћСЃРЅРѕРІРЅРѕР№ файл программы, "точка РІС…РѕРґР°" в”њв”Ђв”Ђ models/ в”‚ в”њв”Ђв”Ђ flight.py # Класс для управления рейсами -в”‚ в”њв”Ђв”Ђ __init__.py +в”‚ в”њв”Ђв”Ђ __init__.py +в”‚ в”њв”Ђв”Ђ flight_types.py +в”‚ в”њв”Ђв”Ђ exceptions.py в”њв”Ђв”Ђ pyproject.toml # Зависимости проекта в”њв”Ђв”Ђ requirements.txt в”њв”Ђв”Ђ .gitignore @@ -83,6 +85,44 @@ airline-ticket-sales-service/ # Корневая директория РїСЂРѕРµ 3. Установить время вылета 4. Установить время прибытия 5. Завершить заполнение + +**Р’РѕРїСЂРѕСЃ:** Как работает класс InternationalFlight? + +Класс InternationalFlight используется для управления международными рейсами. РћРЅ позволяет: + +- устанавливать Рё получать информацию Рѕ необходимости РІРёР·С‹ (visa_required). + +- Получать информацию РѕР±Рѕ всех созданных международных рейсах через метод get_all_instances. + +**Р’РѕРїСЂРѕСЃ:** Как работает класс CharterFlight? + +Класс CharterFlight предназначен для управления чартерными рейсами. РћРЅ позволяет: + +- устанавливать Рё получать информацию РѕР± операторе рейса (operator). + +- получать информацию РѕР±Рѕ всех созданных чартерных рейсах через метод get_all_instances. + +**Р’РѕРїСЂРѕСЃ:** Как работает класс DomesticFlight? + +Класс DomesticFlight используется для управления внутренними рейсами. РћРЅ позволяет: + +- устанавливать Рё получать информацию Рѕ том, является ли рейс лоукостом (is_low_cost). + +- получать информацию РѕР±Рѕ всех созданных внутренних рейсах через метод get_all_instances. + +**Р’РѕРїСЂРѕСЃ:** Как получить информацию РѕР±Рѕ всех рейсах? + +- Для каждого типа рейса (международные, чартерные, внутренние) используйте соответствующий статический метод: + +**InternationalFlight.get_all_instances()** + +**CharterFlight.get_all_instances()** + +**DomesticFlight.get_all_instances()** + +**Р’РѕРїСЂРѕСЃ:** Что делать, если возникает ошибка РїСЂРё установке данных? + +- Если возникает ошибка (например, ValueError), проверьте вводимые данные. ## To do - [ ] Реализовать удобный интерфейс diff --git a/models/flight.py b/models/flight.py index d1f46cfe5ac63a6220aecdbaf2b74457579ad154..2cd6574478d0b6d2e2bc3fca6e65b9830963136c 100644 --- a/models/flight.py +++ b/models/flight.py @@ -1,154 +1,180 @@ +from typing import TypeVar, Generic from datetime import datetime -class Flight: - def __init__(self): - """ - Рнициализирует объект Flight СЃ начальными значениями. - """ - self._flight_number: str | None = None - self._departure: str | None = None - self._destination: str | None = None - self._departure_time: str | None = None - self._arrival_time: str | None = None +class LoggingMixin: + """РњРёРєСЃРёРЅ для логирования действий.""" - def get_flight_number(self) -> str | None: + def log_action(self, action: str) -> None: """ - Возвращает номер рейса. + Логирует действие СЃ указанием класса, РІ котором РѕРЅРѕ произошло. - Возвращает: - str | None: Номер рейса, если РѕРЅ установлен, иначе None. + :param action: Действие, которое РЅСѓР¶РЅРѕ залогировать. """ - return self._flight_number + print(f"[LOG] {action} - {self.__class__.__name__}") - def set_flight_number(self, flight_number: str) -> None: - """ - Устанавливает номер рейса. +class IDMixin: + """РњРёРєСЃРёРЅ для генерации уникальных идентификаторов.""" - Аргументы: - flight_number (str): Номер рейса. Должен быть непустой строкой. - - Вызывает: - ValueError: Если номер рейса РЅРµ является непустой строкой. - """ - if isinstance(flight_number, str) and flight_number.strip(): - self._flight_number = flight_number - else: - raise ValueError("Flight number must be a non-empty string") + _id_counter: int = 0 - def get_departure(self) -> str | None: + def generate_unique_id(self) -> int: """ - Возвращает РїСѓРЅРєС‚ отправления. + Генерирует уникальный идентификатор. - Возвращает: - str | None: РџСѓРЅРєС‚ отправления, если РѕРЅ установлен, иначе None. + :return: Уникальный целочисленный идентификатор. """ - return self._departure + IDMixin._id_counter += 1 + return IDMixin._id_counter - def set_departure(self, departure: str) -> None: - """ - Устанавливает РїСѓРЅРєС‚ отправления. +class FlightMeta(type): + """Метакласс для автоматической регистрации всех классов рейсов.""" - Аргументы: - departure (str): РџСѓРЅРєС‚ отправления. Должен быть непустой строкой. + registered_flights: list = [] - Вызывает: - ValueError: Если РїСѓРЅРєС‚ отправления РЅРµ является непустой строкой. + def __new__(cls, name: str, bases: tuple, dct: dict) -> type: """ - if isinstance(departure, str) and departure.strip(): - self._departure = departure - else: - raise ValueError("The departure point must be a non-empty string") + Создает новый класс Рё регистрирует его, если это РЅРµ базовый класс Flight. - def get_destination(self) -> str | None: + :param name: РРјСЏ класса. + :param bases: Кортеж базовых классов. + :param dct: Словарь атрибутов класса. + :return: Новый класс. """ - Возвращает РїСѓРЅРєС‚ назначения. + new_class = super().__new__(cls, name, bases, dct) + if name != "Flight": + cls.registered_flights.append(new_class) + return new_class - Возвращает: - str | None: РџСѓРЅРєС‚ назначения, если РѕРЅ установлен, иначе None. - """ - return self._destination +FlightNumber = TypeVar("FlightNumber") - def set_destination(self, destination: str) -> None: - """ - Устанавливает РїСѓРЅРєС‚ назначения. +class Flight(LoggingMixin, IDMixin, Generic[FlightNumber], metaclass=FlightMeta): + """Базовый класс для представления рейса.""" - Аргументы: - destination (str): РџСѓРЅРєС‚ назначения. Должен быть непустой строкой. + __slots__ = [ + "_flight_number", + "_departure", + "_destination", + "_departure_time", + "_arrival_time", + "_available_seats", + "_flight_id", + ] - Вызывает: - ValueError: Если РїСѓРЅРєС‚ назначения РЅРµ является непустой строкой. - """ - if isinstance(destination, str) and destination.strip(): - self._destination = destination + def __init__(self) -> None: + """Рнициализирует объект рейса.""" + super().__init__() + self._flight_number: FlightNumber | None = None + self._departure: str | None = None + self._destination: str | None = None + self._departure_time: str | None = None + self._arrival_time: str | None = None + self._available_seats: int = 0 + self._flight_id: int = self.generate_unique_id() + self.log_action("Flight created") + + @property + def flight_number(self) -> FlightNumber | None: + """Возвращает номер рейса.""" + return self._flight_number + + @flight_number.setter + def flight_number(self, value: FlightNumber) -> None: + """Устанавливает номер рейса.""" + if isinstance(value, str) and value.strip(): + self._flight_number = value + self.log_action(f"Flight number set to {value}") else: - raise ValueError("Destination must be a non-empty string") + raise ValueError("Flight number must be a non-empty string") - def get_departure_time(self) -> str | None: - """ - Возвращает время вылета. + @property + def departure(self) -> str | None: + """Возвращает РїСѓРЅРєС‚ отправления.""" + return self._departure - Возвращает: - str | None: Время вылета, если РѕРЅРѕ установлено, иначе None. - """ - return self._departure_time + @departure.setter + def departure(self, value: str) -> None: + """Устанавливает РїСѓРЅРєС‚ отправления.""" + if isinstance(value, str) and value.strip(): + self._departure = value + self.log_action(f"Departure set to {value}") + else: + raise ValueError("The departure point must be a non-empty string") - def set_departure_time(self, departure_time: str) -> None: - """ - Устанавливает время вылета. + @property + def destination(self) -> str | None: + """Возвращает РїСѓРЅРєС‚ назначения.""" + return self._destination - Аргументы: - departure_time (str): Время вылета. Должно быть строкой РІ формате ISO. + @destination.setter + def destination(self, value: str) -> None: + """Устанавливает РїСѓРЅРєС‚ назначения.""" + if isinstance(value, str) and value.strip(): + self._destination = value + self.log_action(f"Destination set to {value}") + else: + raise ValueError("Destination must be a non-empty string") - Вызывает: - ValueError: Если время вылета некорректно или РїРѕР·Р¶Рµ времени прибытия. - """ - if self._is_valid_time(departure_time) and ( - self._arrival_time is None or self._arrival_time > departure_time + @property + def departure_time(self) -> str | None: + """Возвращает время отправления.""" + return self._departure_time + + @departure_time.setter + def departure_time(self, value: str) -> None: + """Устанавливает время отправления.""" + if self._is_valid_time(value) and ( + self._arrival_time is None or value < self._arrival_time ): - self._departure_time = departure_time + self._departure_time = value + self.log_action(f"Departure time set to {value}") else: raise ValueError( "Incorrect departure time or it is later than arrival time" ) - def get_arrival_time(self) -> str | None: - """ - Возвращает время прибытия. - - Возвращает: - str | None: Время прибытия, если РѕРЅРѕ установлено, иначе None. - """ + @property + def arrival_time(self) -> str | None: + """Возвращает время прибытия.""" return self._arrival_time - def set_arrival_time(self, arrival_time: str) -> None: - """ - Устанавливает время прибытия. - - Аргументы: - arrival_time (str): Время прибытия. Должно быть строкой РІ формате ISO. - - Вызывает: - ValueError: Если время прибытия некорректно или раньше времени вылета. - """ - if self._is_valid_time(arrival_time) and ( - self._departure_time is None or arrival_time > self._departure_time + @arrival_time.setter + def arrival_time(self, value: str) -> None: + """Устанавливает время прибытия.""" + if self._is_valid_time(value) and ( + self._departure_time is None or value > self._departure_time ): - self._arrival_time = arrival_time + self._arrival_time = value + self.log_action(f"Arrival time set to {value}") else: raise ValueError( "Incorrect arrival time or it is earlier than departure time" ) + @property + def available_seats(self) -> int: + """Возвращает количество свободных мест.""" + return self._available_seats + + @available_seats.setter + def available_seats(self, value: int) -> None: + """Устанавливает количество свободных мест.""" + if value < 0: + raise ValueError("Available seats cannot be negative") + self._available_seats = value + self.log_action(f"Available seats set to {value}") + + @property + def flight_id(self) -> int: + """Возвращает уникальный ID рейса.""" + return self._flight_id + def _is_valid_time(self, time_str: str) -> bool: """ Проверяет, является ли строка времени корректной РІ формате ISO. - Аргументы: - time_str (str): Строка времени для проверки. - - Возвращает: - bool: True, если строка времени корректна, иначе False. + :param time_str: Строка времени для проверки. + :return: True, если строка времени корректна, иначе False. """ try: datetime.fromisoformat(time_str) @@ -156,12 +182,11 @@ class Flight: except ValueError: return False - def get_flight_info(self) -> dict[str, str | None]: + def get_flight_info(self) -> dict: """ Возвращает информацию Рѕ рейсе РІ РІРёРґРµ словаря. - Возвращает: - dict[str, str | None]: Словарь, содержащий информацию Рѕ рейсе. + :return: Словарь СЃ информацией Рѕ рейсе. """ return { "flight_number": self._flight_number, @@ -169,4 +194,23 @@ class Flight: "destination": self._destination, "departure_time": self._departure_time, "arrival_time": self._arrival_time, + "available_seats": self._available_seats, } + + +class FlightAvailability: + """Класс для проверки доступности рейса.""" + + @staticmethod + def check_availability(flight: Flight) -> bool: + """ + Проверяет, доступен ли рейс для бронирования. + + :param flight: Рейс для проверки. + :return: True, если рейс доступен, иначе False. + """ + if not flight.flight_number: + return False + if flight.available_seats == 0: + return False + return True diff --git a/models/flight_types.py b/models/flight_types.py index 0dece7299a60ba9d53c23f79907a2f5bf83de460..fccf71f316b65096524c81dc45545ec3879bf3ae 100644 --- a/models/flight_types.py +++ b/models/flight_types.py @@ -1,184 +1,94 @@ -from models.flight import Flight +from models.flight import Flight, FlightNumber -class InternationalFlight(Flight): - __instances = [] +class InternationalFlight(Flight[FlightNumber]): + __slots__ = ["_visa_required"] + _instances = [] def __init__(self): - """ - Рнициализирует объект InternationalFlight. - """ super().__init__() - self.__visa_required: bool | None = None - InternationalFlight.__instances.append(self) - - def get_visa_required(self) -> bool | None: - """ - Возвращает информацию Рѕ необходимости РІРёР·С‹. - - Returns: - bool | None: True, если РІРёР·Р° требуется, иначе False или None. - """ - return self.__visa_required - - def set_visa_required(self, visa_required: bool) -> None: - """ - Устанавливает необходимость РІРёР·С‹. - - Args: - visa_required (bool): True, если РІРёР·Р° требуется, иначе False. - - Raises: - ValueError: Если значение РЅРµ является булевым. - """ - if isinstance(visa_required, bool): - self.__visa_required = visa_required - else: - raise ValueError("Visa required must be a boolean") + self._visa_required: bool | None = None + InternationalFlight._instances.append(self) - def get_flight_info(self) -> dict[str, str | bool | None]: - """ - Возвращает информацию Рѕ рейсе. + @property + def visa_required(self) -> bool | None: + """Возвращает, требуется ли РІРёР·Р° для международного рейса.""" + return self._visa_required - Returns: - dict[str, str | bool | None]: Словарь СЃ информацией Рѕ рейсе. - """ - base_info = super().get_flight_info() - base_info["visa_required"] = self.__visa_required - return base_info + @visa_required.setter + def visa_required(self, value: bool) -> None: + """Устанавливает, требуется ли РІРёР·Р° для международного рейса.""" + self._visa_required = value + self.log_action(f"Visa required set to {value}") - @staticmethod - def get_all_instances() -> list[tuple[dict[str, str | bool | None], str]]: + def get_flight_info(self) -> dict: """ - Возвращает СЃРїРёСЃРѕРє всех созданных объектов InternationalFlight СЃ РёС… информацией. + Возвращает информацию Рѕ международном рейсе. - Returns: - list[tuple[dict[str, str | bool | None], str]]: - РЎРїРёСЃРѕРє кортежей СЃ информацией Рѕ рейсе Рё его типе. + :return: Словарь СЃ информацией Рѕ рейсе, включая необходимость РІРёР·С‹. """ - return [ - (flight.get_flight_info(), "International") - for flight in InternationalFlight.__instances - ] + info = super().get_flight_info() + info["visa_required"] = self._visa_required + return info - -class CharterFlight(Flight): - __instances = [] +class CharterFlight(Flight[FlightNumber]): + __slots__ = ["_operator"] + _instances = [] def __init__(self): - """ - Рнициализирует объект CharterFlight. - """ super().__init__() - self.__operator: str | None = None - CharterFlight.__instances.append(self) + self._operator: str | None = None + CharterFlight._instances.append(self) - def get_operator(self) -> str | None: - """ - Возвращает оператора чартерного рейса. + @property + def operator(self) -> str | None: + """Возвращает оператора чартерного рейса.""" + return self._operator - Returns: - str | None: Название оператора или None. - """ - return self.__operator + @operator.setter + def operator(self, value: str) -> None: + """Устанавливает оператора чартерного рейса.""" + self._operator = value + self.log_action(f"Operator set to {value}") - def set_operator(self, operator: str) -> None: + def get_flight_info(self) -> dict: """ - Устанавливает оператора чартерного рейса. + Возвращает информацию Рѕ чартерном рейсе. - Args: - operator (str): Название оператора. - - Raises: - ValueError: Если название оператора РЅРµ является непустой строкой. + :return: Словарь СЃ информацией Рѕ рейсе, включая оператора. """ - if isinstance(operator, str) and operator.strip(): - self.__operator = operator - else: - raise ValueError("Operator must be a non-empty string") + info = super().get_flight_info() + info["operator"] = self._operator + return info - def get_flight_info(self) -> dict[str, str | None]: - """ - Возвращает информацию Рѕ рейсе. - Returns: - dict[str, str | None]: Словарь СЃ информацией Рѕ рейсе. - """ - base_info = super().get_flight_info() - base_info["operator"] = self.__operator - return base_info - - @staticmethod - def get_all_instances() -> list[tuple[dict[str, str | None], str]]: - """ - Возвращает СЃРїРёСЃРѕРє всех созданных объектов CharterFlight СЃ РёС… информацией. - - Returns: - list[tuple[dict[str, str | None], str]]: - РЎРїРёСЃРѕРє кортежей СЃ информацией Рѕ рейсе Рё его типе. - """ - return [ - (flight.get_flight_info(), "Charter") - for flight in CharterFlight.__instances - ] +class DomesticFlight(Flight[FlightNumber]): + __slots__ = ["_is_low_cost"] + _instances = [] -class DomesticFlight(Flight): - __instances = [] - def __init__(self): - """ - Рнициализирует объект DomesticFlight. - """ super().__init__() - self.__is_low_cost: bool | None = None - DomesticFlight.__instances.append(self) + self._is_low_cost: bool | None = None + DomesticFlight._instances.append(self) - def get_is_low_cost(self) -> bool | None: - """ - Возвращает информацию Рѕ том, является ли рейс лоукост. + @property + def is_low_cost(self) -> bool | None: + """Возвращает, является ли рейс низкобюджетным.""" + return self._is_low_cost - Returns: - bool | None: True, если рейс лоукост, иначе False или None. - """ - return self.__is_low_cost - - def set_is_low_cost(self, is_low_cost: bool) -> None: - """ - Устанавливает, является ли рейс лоукост. - - Args: - is_low_cost (bool): True, если рейс лоукост, иначе False. - - Raises: - ValueError: Если значение РЅРµ является булевым. - """ - if isinstance(is_low_cost, bool): - self.__is_low_cost = is_low_cost - else: - raise ValueError("Is low cost must be a boolean") - - def get_flight_info(self) -> dict[str, str | bool | None]: - """ - Возвращает информацию Рѕ рейсе. - - Returns: - dict[str, str | bool | None]: Словарь СЃ информацией Рѕ рейсе. - """ - base_info = super().get_flight_info() - base_info["is_low_cost"] = self.__is_low_cost - return base_info + @is_low_cost.setter + def is_low_cost(self, value: bool) -> None: + """Устанавливает, является ли рейс низкобюджетным.""" + self._is_low_cost = value + self.log_action(f"Is low cost set to {value}") - @staticmethod - def get_all_instances() -> list[tuple[dict[str, str | bool | None], str]]: + def get_flight_info(self) -> dict: """ - Возвращает СЃРїРёСЃРѕРє всех созданных объектов DomesticFlight СЃ РёС… информацией. + Возвращает информацию Рѕ внутреннем рейсе. - Returns: - list[tuple[dict[str, str | bool | None], str]]: - РЎРїРёСЃРѕРє кортежей СЃ информацией Рѕ рейсе Рё его типе. + :return: Словарь СЃ информацией Рѕ рейсе, включая флаг низкобюджетности. """ - return [ - (flight.get_flight_info(), "Domestic") - for flight in DomesticFlight.__instances - ] + info = super().get_flight_info() + info["is_low_cost"] = self._is_low_cost + return info