diff --git a/inpost/api.py b/inpost/api.py index f79ff6b..e445c75 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -19,7 +19,7 @@ def __init__(self): self._log: logging.Logger | None = None def __repr__(self): - return f'Phone number: {self.phone_number}\nToken: {self.auth_token}' + return f'{self.__class__.__name__}(phone_number={self.phone_number})' @classmethod async def from_phone_number(cls, phone_number: str | int): @@ -87,13 +87,6 @@ async def send_sms_code(self) -> bool: raise UnidentifiedAPIError(reason=phone) - # if phone.status == 200: - # self._log.debug(f'sms code sent') - # return True - # else: - # self._log.error(f'could not sent sms code') - # raise PhoneNumberError(reason=phone) - async def confirm_sms_code(self, sms_code: str | int) -> bool: """Confirms sms code sent to `Inpost.phone_number` and fetches tokens @@ -143,17 +136,6 @@ async def confirm_sms_code(self, sms_code: str | int) -> bool: raise UnidentifiedAPIError(reason=confirmation) - # if confirmation.status == 200: - # resp = await confirmation.json() - # self.sms_code = sms_code - # self.refr_token = resp['refreshToken'] - # self.auth_token = resp['authToken'] - # self._log.debug(f'sms code confirmed') - # return True - # else: - # self._log.error(f'could not confirm sms code') - # raise SmsCodeConfirmationError(reason=confirmation) - async def refresh_token(self) -> bool: """Refreshes authorization token using refresh token @@ -197,19 +179,6 @@ async def refresh_token(self) -> bool: raise UnidentifiedAPIError(reason=confirmation) - # if confirmation.status == 200: - # resp = await confirmation.json() - # if resp['reauthenticationRequired']: - # self._log.error(f'could not refresh token, log in again') - # raise ReAuthenticationError(reason='You need to log in again!') - # self.auth_token = resp['authToken'] - # self._log.debug(f'token refreshed') - # return True - # - # else: - # self._log.error(f'could not refresh token') - # raise RefreshTokenException(reason=confirmation) - async def logout(self) -> bool: """Logouts user from inpost api service @@ -246,17 +215,6 @@ async def logout(self) -> bool: raise UnidentifiedAPIError(reason=resp) - # if resp.status == 200: - # self.phone_number = None - # self.refr_token = None - # self.auth_token = None - # self.sms_code = None - # self._log.debug('logged out') - # return True - # else: - # self._log.error('could not log out') - # raise UnidentifiedAPIError(reason=resp) - async def disconnect(self) -> bool: """Simplified method to logout and close user's session @@ -311,13 +269,6 @@ async def get_parcel(self, shipment_number: int | str, parse=False) -> dict | Pa self._log.error(f'could not get parcel with shipment number {shipment_number}, unhandled status') raise UnidentifiedAPIError(reason=resp) - # if resp.status == 200: - # self._log.debug(f'parcel with shipment number {shipment_number} received') - # return await resp.json() if not parse else Parcel(await resp.json(), logger=self._log) - # - # else: - # self._log.error(f'could not get parcel with shipment number {shipment_number}') - # raise UnidentifiedAPIError(reason=resp) async def get_parcels(self, parcel_type: ParcelType = ParcelType.TRACKED, @@ -423,48 +374,6 @@ async def get_parcels(self, raise UnidentifiedAPIError(reason=resp) - # if resp.status == 200: - # self._log.debug(f'received {parcel_type} parcels') - # _parcels = (await resp.json())['parcels'] - # - # if status is not None: - # if isinstance(status, ParcelStatus): - # status = [status] - # - # _parcels = (_parcel for _parcel in _parcels if ParcelStatus[_parcel['status']] in status) - # - # if pickup_point is not None: - # if isinstance(pickup_point, str): - # pickup_point = [pickup_point] - # - # _parcels = (_parcel for _parcel in _parcels if _parcel['pickUpPoint']['name'] in pickup_point) - # - # if shipment_type is not None: - # if isinstance(shipment_type, ParcelShipmentType): - # shipment_type = [shipment_type] - # - # _parcels = (_parcel for _parcel in _parcels if - # ParcelShipmentType[_parcel['shipmentType']] in shipment_type) - # - # if parcel_size is not None: - # if isinstance(parcel_size, ParcelCarrierSize): - # parcel_size = [parcel_size] - # - # _parcels = (_parcel for _parcel in _parcels if - # ParcelCarrierSize[_parcel['parcelSize']] in parcel_size) - # - # if isinstance(parcel_size, ParcelLockerSize): - # parcel_size = [parcel_size] - # - # _parcels = (_parcel for _parcel in _parcels if - # ParcelLockerSize[_parcel['parcelSize']] in parcel_size) - # - # return _parcels if not parse else [Parcel(parcel_data=data, logger=self._log) for data in _parcels] - # - # else: - # self._log.error(f'could not get parcels') - # raise UnidentifiedAPIError(reason=resp) - async def collect_compartment_properties(self, shipment_number: str | int | None = None, parcel_obj: Parcel | None = None, location: dict | None = None) -> bool: """Validates sent data and fetches required compartment properties for opening @@ -522,16 +431,6 @@ async def collect_compartment_properties(self, shipment_number: str | int | None raise UnidentifiedAPIError(reason=collect_resp) - # if collect_resp.status == 200: - # self._log.debug(f'collected compartment properties for {shipment_number}') - # parcel_obj.compartment_properties = await collect_resp.json() - # self.parcel = parcel_obj - # return True - # - # else: - # self._log.error(f'could not collect compartment properties for {shipment_number}') - # raise UnidentifiedAPIError(reason=collect_resp) - async def open_compartment(self) -> bool: """Opens compartment for `Inpost.parcel` object @@ -568,15 +467,6 @@ async def open_compartment(self) -> bool: raise UnidentifiedAPIError(reason=compartment_open_resp) - # if compartment_open_resp.status == 200: - # self._log.debug(f'opened comaprtment for {self.parcel.shipment_number}') - # self.parcel.compartment_properties.location = await compartment_open_resp.json() - # return True - # - # else: - # self._log.error(f'could not open compartment for {self.parcel.shipment_number}') - # raise UnidentifiedAPIError(reason=compartment_open_resp) - async def check_compartment_status(self, expected_status: CompartmentExpectedStatus = CompartmentExpectedStatus.OPENED) -> bool: """Checks and compare compartment status (e.g. opened, closed) with expected status @@ -608,6 +498,7 @@ async def check_compartment_status(self, match compartment_status_resp.status: case 200: self._log.debug(f'checked compartment status for {self.parcel.shipment_number}') + self.parcel.compartment_status = (await compartment_status_resp.json())['status'] return CompartmentExpectedStatus[ (await compartment_status_resp.json())['status']] == expected_status case 401: @@ -623,13 +514,6 @@ async def check_compartment_status(self, raise UnidentifiedAPIError(reason=compartment_status_resp) - # if compartment_status_resp.status == 200: - # self._log.debug(f'checked compartment status for {self.parcel.shipment_number}') - # return CompartmentExpectedStatus[(await compartment_status_resp.json())['status']] == expected_status - # else: - # self._log.error(f'could not check compartment status for {self.parcel.shipment_number}') - # raise UnidentifiedAPIError(reason=compartment_status_resp) - async def terminate_collect_session(self) -> bool: """Terminates user session in inpost api service @@ -667,13 +551,6 @@ async def terminate_collect_session(self) -> bool: raise UnidentifiedAPIError(reason=terminate_resp) - # if terminate_resp.status == 200: - # self._log.debug(f'terminated collect session for {self.parcel.shipment_number}') - # return True - # else: - # self._log.error(f'could not terminate collect session for {self.parcel.shipment_number}') - # raise UnidentifiedAPIError(reason=terminate_resp) - async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, location: dict | None = None) -> bool: """Simplified method to open compartment @@ -694,8 +571,6 @@ async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | .. warning:: you must fill in only one parameter - shipment_number or parcel_obj!""" - self._log.info(f'collecing parcel with shipment number {self.parcel.shipment_number}') - if shipment_number and parcel_obj: self._log.error(f'shipment_number and parcel_obj filled in') raise SingleParamError(reason='Fields shipment_number and parcel_obj filled! Choose one!') @@ -707,6 +582,8 @@ async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | if shipment_number is not None and parcel_obj is None: parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + self._log.info(f'collecing parcel with shipment number {parcel_obj.shipment_number}') + if await self.collect_compartment_properties(parcel_obj=parcel_obj, location=location): if await self.open_compartment(): if await self.check_compartment_status(): @@ -758,11 +635,3 @@ async def get_prices(self) -> dict: self._log.error('could not get parcel prices, unhandled status') raise UnidentifiedAPIError(reason=resp) - - # if resp.status == 200: - # self._log.debug(f'got parcel prices') - # return await resp.json() - # - # else: - # self._log.error('could not get parcel prices') - # raise UnidentifiedAPIError(reason=resp) diff --git a/inpost/static/parcels.py b/inpost/static/parcels.py index 8f8c00d..419e3d4 100644 --- a/inpost/static/parcels.py +++ b/inpost/static/parcels.py @@ -27,6 +27,7 @@ def __init__(self, parcel_data: dict, logger: logging.Logger): if 'qrCode' in parcel_data else None self.stored_date: arrow | None = get(parcel_data['storedDate']) if 'storedDate' in parcel_data else None self.pickup_date: arrow | None = get(parcel_data['pickUpDate']) if 'pickUpDate' in parcel_data else None + self.expiry_date: arrow | None = get(parcel_data['expiryDate']) if 'expiryDate' in parcel_data else None self.parcel_size: ParcelLockerSize | ParcelCarrierSize = ParcelLockerSize[parcel_data['parcelSize']] \ if self.shipment_type == ParcelShipmentType.parcel else ParcelCarrierSize[parcel_data['parcelSize']] self.receiver: Receiver = Receiver(receiver_data=parcel_data['receiver'], logger=self._log) @@ -61,6 +62,10 @@ def __init__(self, parcel_data: dict, logger: logging.Logger): if self.ownership_status == ParcelOwnership.UNKNOWN: self._log.debug(f'unexpected ownership status: {parcel_data["ownershipStatus"]}') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + def __str__(self): return f"Sender: {str(self.sender)}\n" \ f"Shipment number: {self.shipment_number}\n" \ @@ -132,22 +137,22 @@ def compartment_location(self): self._log.debug('getting compartment location') if self.shipment_type == ParcelShipmentType.parcel: self._log.debug('got compartment location') - return self._compartment_properties.location + return self._compartment_properties.location if self._compartment_properties else None self._log.debug('wrong ParcelShipmentType') return None - # @compartment_location.setter - # def compartment_location(self, location_data): - # """Set compartment location for :class:`Parcel` - # :param location_data: :class:`dict` containing `compartment properties` data for :class:`Parcel` - # :type location_data: CompartmentProperties""" - # self._log.debug('setting compartment location') - # if self.shipment_type == ParcelShipmentType.parcel: - # self._log.debug('compartment location set') - # self._compartment_properties.location = location_data - # - # self._log.debug('wrong ParcelShipmentType') + @compartment_location.setter + def compartment_location(self, location_data): + """Set compartment location for :class:`Parcel` + :param location_data: :class:`dict` containing `compartment properties` data for :class:`Parcel` + :type location_data: CompartmentProperties""" + self._log.debug('setting compartment location') + if self.shipment_type == ParcelShipmentType.parcel: + self._log.debug('compartment location set') + self._compartment_properties.location = location_data + + self._log.debug('wrong ParcelShipmentType') @property def compartment_status(self) -> CompartmentActualStatus | None: @@ -156,21 +161,22 @@ def compartment_status(self) -> CompartmentActualStatus | None: :return: Compartment status for :class:`Parcel` :rtype: CompartmentActualStatus""" self._log.debug('getting compartment status') + if self.shipment_type == ParcelShipmentType.parcel: self._log.debug('got compartment status') - return self._compartment_properties.status + return self._compartment_properties.status if self._compartment_properties else None self._log.debug('wrong ParcelShipmentType') return None - # @compartment_status.setter - # def compartment_status(self, status): - # self._log.debug('setting compartment status') - # if self.shipment_type == ParcelShipmentType.parcel: - # self._log.debug('compartment status set') - # self._compartment_properties.status = status - # - # self._log.debug('wrong ParcelShipmentType') + @compartment_status.setter + def compartment_status(self, status): + self._log.debug('setting compartment status') + if self.shipment_type == ParcelShipmentType.parcel: + self._log.debug('compartment status set') + self._compartment_properties.status = status + + self._log.debug('wrong ParcelShipmentType') @property def compartment_open_data(self): @@ -216,6 +222,7 @@ class Receiver: :type receiver_data: dict :param logger: :class:`logging.Logger` parent instance :type logger: logging.Logger""" + def __init__(self, receiver_data: dict, logger: logging.Logger): """Constructor method""" self.email: str = receiver_data['email'] @@ -225,6 +232,10 @@ def __init__(self, receiver_data: dict, logger: logging.Logger): self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class Sender: """Object representation of :class:`Parcel` sender @@ -241,6 +252,10 @@ def __init__(self, sender_data: dict, logger: logging.Logger): self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + def __str__(self) -> str: return self.sender_name @@ -280,6 +295,10 @@ def __init__(self, pickuppoint_data: dict, logger: logging.Logger): if ParcelDeliveryType.UNKNOWN in self.type: self._log.debug(f'unknown delivery type: {pickuppoint_data["type"]}') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + def __str__(self) -> str: return self.name @@ -312,6 +331,10 @@ def __init__(self, multicompartment_data: dict, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class Operations: """Object representation of :class:`Parcel` `operations` @@ -340,6 +363,10 @@ def __init__(self, operations_data: dict, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class EventLog: """Object representation of :class:`Parcel` single eventlog @@ -361,6 +388,10 @@ def __init__(self, eventlog_data: dict, logger: logging.Logger): if self.name == ParcelStatus.UNKNOWN: self._log.debug(f'unknown parcel status: {eventlog_data["name"]}') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class SharedTo: """Object representation of :class:`Parcel` single shared to @@ -379,6 +410,10 @@ def __init__(self, sharedto_data: dict, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class QRCode: """Object representation of :class:`Parcel` QRCode @@ -395,6 +430,10 @@ def __init__(self, qrcode_data: str, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + @property def qr_image(self) -> BytesIO: """Returns a generated QR image for :class:`QRCode` @@ -442,6 +481,10 @@ def __init__(self, compartmentlocation_data: dict, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + class CompartmentProperties: """Object representation of :class:`Parcel` compartment properties @@ -461,6 +504,10 @@ def __init__(self, compartmentproperties_data: dict, logger: logging.Logger): self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + @property def session_uuid(self): """Returns a session unique identified for :class:`CompartmentProperties` @@ -497,11 +544,11 @@ def status(self): self._log.debug('getting status') return self._status - # @status.setter - # def status(self, status_data: str | CompartmentActualStatus): - # self._log.debug('setting status') - # self._status = status_data if isinstance(status_data, CompartmentActualStatus) \ - # else CompartmentActualStatus[status_data] - # - # if self._status == CompartmentActualStatus.UNKNOWN and isinstance(status_data, str): - # self._log.debug(f'unexpected compartment actual status: {status_data}') + @status.setter + def status(self, status_data: str | CompartmentActualStatus): + self._log.debug('setting status') + self._status = status_data if isinstance(status_data, CompartmentActualStatus) \ + else CompartmentActualStatus[status_data] + + if self._status == CompartmentActualStatus.UNKNOWN and isinstance(status_data, str): + self._log.debug(f'unexpected compartment actual status: {status_data}') diff --git a/inpost/static/statuses.py b/inpost/static/statuses.py index c98eb49..d3b3158 100644 --- a/inpost/static/statuses.py +++ b/inpost/static/statuses.py @@ -1,4 +1,5 @@ from enum import Enum, EnumMeta +from typing import List class Meta(EnumMeta): # temporary handler for unexpected keys in enums @@ -8,15 +9,29 @@ def __getitem__(cls, item): except KeyError as error: return cls.UNKNOWN - def __getattr__(cls, item): + def __getattribute__(cls, item): try: return super().__getattribute__(item) except KeyError as error: return cls.UNKNOWN + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") + + def get_all(cls): + return [getattr(cls, name) for name in cls.__members__] + + def get_without(cls, without: 'ParcelBase' | List['ParcelBase']): + if isinstance(without, ParcelBase): + without = [without] + + return [element for element in cls.get_all() if element not in without] + class ParcelBase(Enum, metaclass=Meta): """Base :class:`Enum` class to derive from""" + def __gt__(self, other): if isinstance(other, ParcelBase): ... @@ -118,7 +133,7 @@ class ParcelStatus(ParcelBase): READY_TO_PICKUP = 'Gotowa do odbioru' PICKUP_REMINDER_SENT = 'Wysłano przypomnienie o odbiorze' # TODO: translate from app PICKUP_TIME_EXPIRED = 'Upłynął czas odbioru' # TODO: translate from app - AVIZO = 'Awizo' # TODO: translate from app + AVIZO = 'Powrót do oddziału' TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' REJECTED_BY_RECEIVER = 'Odrzucona przez odbiorcę' # TODO: translate from app UNDELIVERED = 'Nie dostarczona' # TODO: translate from app @@ -127,7 +142,7 @@ class ParcelStatus(ParcelBase): READY_TO_PICKUP_FROM_BRANCH = 'Gotowa do odbioru z oddziału' # TODO: translate from app DELIVERED = 'Doręczona' CANCELED = 'Anulowana' # TODO: translate from app - CLAIMED = 'Przejęta' # TODO: translate from app + CLAIMED = 'Zareklamowana' STACK_IN_CUSTOMER_SERVICE_POINT = 'Umieszczona w punkcie obsługi klienta' # TODO: translate from app STACK_PARCEL_PICKUP_TIME_EXPIRED = 'Upłynął czas odbioru' # TODO: translate from app UNSTACK_FROM_CUSTOMER_SERVICE_POINT = '?' # TODO: translate from app diff --git a/pyproject.toml b/pyproject.toml index 6917e9c..8dd2a1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,10 @@ [tool.poetry] name = "inpost" -version = "0.0.4" +version = "0.0.5" description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app" authors = ["loboda4450 ", "MrKazik99 "] maintainers = ["loboda4450 "] +documentation = 'https://inpost-python.readthedocs.io/en/latest/index.html' repository = "https://github.com/IFOSSA/inpost-python" readme = "README.md" packages = [