From 60e8933ce01833a3af2e8f6639559a019ff19845 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 1 Feb 2023 11:12:30 +0100 Subject: [PATCH 01/14] new endpoint --- inpost/static/__init__.py | 2 +- inpost/static/endpoints.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/inpost/static/__init__.py b/inpost/static/__init__.py index 6032d26..77d7bd1 100644 --- a/inpost/static/__init__.py +++ b/inpost/static/__init__.py @@ -9,4 +9,4 @@ UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError from .endpoints import login, send_sms_code, confirm_sms_code, refresh_token, parcels, parcel, collect, \ compartment_open, compartment_status, terminate_collect_session, friends, shared, sent, returns, parcel_prices, \ - tickets, logout + tickets, logout, multi diff --git a/inpost/static/endpoints.py b/inpost/static/endpoints.py index da566fe..5a2599a 100644 --- a/inpost/static/endpoints.py +++ b/inpost/static/endpoints.py @@ -7,6 +7,7 @@ refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get parcel: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/' # get +multi: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/multi/' # get collect: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/validate' # post compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post From c70523c7c42090de59035fafd501aaf5e2321620 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 1 Feb 2023 12:06:35 +0100 Subject: [PATCH 02/14] removed logger from repr, better multicompartment support --- inpost/static/parcels.py | 69 ++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/inpost/static/parcels.py b/inpost/static/parcels.py index 419e3d4..a0bb70f 100644 --- a/inpost/static/parcels.py +++ b/inpost/static/parcels.py @@ -63,7 +63,7 @@ def __init__(self, parcel_data: dict, logger: logging.Logger): 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()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") def __str__(self): @@ -83,7 +83,7 @@ def open_code(self) -> str | None: self._log.debug('got open code') return self._open_code - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @property @@ -97,7 +97,7 @@ def generate_qr_image(self) -> BytesIO | None: self._log.debug('got qr image') return self._qr_code.qr_image - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @property @@ -111,7 +111,7 @@ def compartment_properties(self): self._log.debug('got compartment properties') return self._compartment_properties - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @compartment_properties.setter @@ -126,7 +126,7 @@ def compartment_properties(self, compartmentproperties_data: dict): self._compartment_properties = CompartmentProperties(compartmentproperties_data=compartmentproperties_data, logger=self._log) - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') @property def compartment_location(self): @@ -139,20 +139,20 @@ def compartment_location(self): self._log.debug('got compartment location') return self._compartment_properties.location if self._compartment_properties else None - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @compartment_location.setter - def compartment_location(self, location_data): + def compartment_location(self, location_data: dict): """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') + self._log.debug(f'setting compartment location with {location_data}') if self.shipment_type == ParcelShipmentType.parcel: self._log.debug('compartment location set') self._compartment_properties.location = location_data - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') @property def compartment_status(self) -> CompartmentActualStatus | None: @@ -166,17 +166,17 @@ def compartment_status(self) -> CompartmentActualStatus | None: self._log.debug('got compartment status') return self._compartment_properties.status if self._compartment_properties else None - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @compartment_status.setter def compartment_status(self, status): - self._log.debug('setting compartment status') + self._log.debug(f'setting compartment status with {status}') if self.shipment_type == ParcelShipmentType.parcel: self._log.debug('compartment status set') self._compartment_properties.status = status - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') @property def compartment_open_data(self): @@ -193,7 +193,7 @@ def compartment_open_data(self): 'receiverPhoneNumber': self.receiver.phone_number } - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None @property @@ -211,9 +211,30 @@ def mocked_location(self): 'accuracy': round(random.uniform(1, 4), 1) } - self._log.debug('wrong ParcelShipmentType') + self._log.debug(f'wrong ParcelShipmentType: {repr(self.shipment_type)}') return None + @property + def is_multicompartment(self): + """Specifies if parcel is in multi compartment + :return: True if parcel is in multicompartment + :rtype: bool""" + return self.multi_compartment is not None + + @property + def is_main_multicompartment(self): + """Specifies if parcel is main parcel in multi compartment + :return: True if parcel is in multicompartment + :rtype: bool""" + if self.is_multicompartment: + return self.multi_compartment.shipment_numbers is not None + + return None + + # @property + # def get_from_multicompartment(self): + # return + class Receiver: """Object representation of :class:`Parcel` receiver @@ -233,7 +254,7 @@ 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()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -253,7 +274,7 @@ 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()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") def __str__(self) -> str: @@ -296,7 +317,7 @@ def __init__(self, pickuppoint_data: dict, logger: logging.Logger): 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()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") def __str__(self) -> str: @@ -332,7 +353,7 @@ def __init__(self, multicompartment_data: dict, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -364,7 +385,7 @@ def __init__(self, operations_data: dict, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -389,7 +410,7 @@ def __init__(self, eventlog_data: dict, logger: logging.Logger): 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()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -411,7 +432,7 @@ def __init__(self, sharedto_data: dict, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -431,7 +452,7 @@ def __init__(self, qrcode_data: str, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @property @@ -482,7 +503,7 @@ def __init__(self, compartmentlocation_data: dict, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @@ -505,7 +526,7 @@ def __init__(self, compartmentproperties_data: dict, logger: logging.Logger): self._log.debug('created') def __repr__(self): - fields = tuple(f"{k}={v}" for k, v in self.__dict__.items()) + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") @property From 50b88bc40fed75002b4aa5923ca700a6f681f72e Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 1 Feb 2023 12:07:18 +0100 Subject: [PATCH 03/14] multicompartment support --- inpost/api.py | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/inpost/api.py b/inpost/api.py index e445c75..772ad5f 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -21,6 +21,12 @@ def __init__(self): def __repr__(self): return f'{self.__class__.__name__}(phone_number={self.phone_number})' + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + return self.logout() + @classmethod async def from_phone_number(cls, phone_number: str | int): """`Classmethod` to initialize :class:`Inpost` object with phone number @@ -340,7 +346,8 @@ async def get_parcels(self, if isinstance(pickup_point, str): pickup_point = [pickup_point] - _parcels = (_parcel for _parcel in _parcels if _parcel['pickUpPoint']['name'] in 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): @@ -362,7 +369,8 @@ async def get_parcels(self, _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] + return _parcels if not parse else [Parcel(parcel_data=data, logger=self._log) for data in + _parcels] case 401: self._log.error(f'could not get parcels, unauthorized') raise UnauthorizedError(reason=resp) @@ -374,6 +382,29 @@ async def get_parcels(self, raise UnidentifiedAPIError(reason=resp) + async def get_multi_compartment(self, multi_uuid: str | int, parse: bool = False) -> dict | List[Parcel]: + if not self.auth_token: + self._log.error(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.get(url=f"{multi}{multi_uuid}", + headers={'Authorization': self.auth_token}, + ) as resp: + match resp.status: + case 200: + self._log.debug(f'parcel with multicompartment uuid {multi_uuid} received') + return await resp.json() if not parse else [Parcel(data, logger=self._log) for data in (await resp.json())['parcels']] + case 401: + self._log.error(f'could not get parcel with multicompartment uuid {multi_uuid}, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error(f'could not get parcel with multicompartment uuid {multi_uuid}, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error(f'could not get parcel with multicompartment uuid {multi_uuid}, unhandled status') + + 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 @@ -394,8 +425,6 @@ async def collect_compartment_properties(self, shipment_number: str | int | None .. warning:: you must fill in only one parameter - shipment_number or parcel_obj!""" - self._log.info(f'collecting compartment properties for {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 in! Choose one!') @@ -408,6 +437,8 @@ async def collect_compartment_properties(self, shipment_number: str | int | None self._log.debug(f'parcel_obj not provided, getting from shipment number {shipment_number}') parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + self._log.info(f'collecting compartment properties for {parcel_obj.shipment_number}') + async with await self.sess.post(url=collect, headers={'Authorization': self.auth_token}, json={ @@ -416,18 +447,21 @@ async def collect_compartment_properties(self, shipment_number: str | int | None }) as collect_resp: match collect_resp.status: case 200: - self._log.debug(f'collected compartment properties for {shipment_number}') + self._log.debug(f'collected compartment properties for {parcel_obj.shipment_number}') parcel_obj.compartment_properties = await collect_resp.json() self.parcel = parcel_obj return True case 401: - self._log.error(f'could not collect compartment properties for {shipment_number}, unauthorized') + self._log.error(f'could not collect compartment properties for {parcel_obj.shipment_number}, ' + f'unauthorized') raise UnauthorizedError(reason=collect_resp) case 404: - self._log.error(f'could not collect compartment properties for {shipment_number}, not found') + self._log.error(f'could not collect compartment properties for {parcel_obj.shipment_number}, not ' + f'found') raise NotFoundError(reason=collect_resp) case _: - self._log.error(f'could not collect compartment properties for {shipment_number}, unhandled status') + self._log.error(f'could not collect compartment properties for {parcel_obj.shipment_number}, ' + f'unhandled status') raise UnidentifiedAPIError(reason=collect_resp) From 7b5460f52d07e8dd694402e3053a3d280e63f905 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Wed, 1 Feb 2023 12:08:47 +0100 Subject: [PATCH 04/14] version 0.0.6 --- inpost/static/statuses.py | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/inpost/static/statuses.py b/inpost/static/statuses.py index d3b3158..0d99d2e 100644 --- a/inpost/static/statuses.py +++ b/inpost/static/statuses.py @@ -15,10 +15,6 @@ def __getattribute__(cls, 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__] @@ -62,6 +58,10 @@ def __eq__(self, other): return False + 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 ParcelCarrierSize(ParcelBase): """:class:`Enum` that holds parcel size for carrier shipment type""" diff --git a/pyproject.toml b/pyproject.toml index 8dd2a1e..909eef0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "inpost" -version = "0.0.5" +version = "0.0.6" description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app" authors = ["loboda4450 ", "MrKazik99 "] maintainers = ["loboda4450 "] From 4ae5f2720339a2a95c82bc7ae8fa924f6223ba29 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 9 Feb 2023 11:49:27 +0100 Subject: [PATCH 05/14] friends support --- inpost/api.py | 170 +++++++++++++++++++++++++++++++++++- inpost/static/__init__.py | 2 +- inpost/static/exceptions.py | 5 ++ 3 files changed, 175 insertions(+), 2 deletions(-) diff --git a/inpost/api.py b/inpost/api.py index 772ad5f..ca9aa35 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -393,7 +393,8 @@ async def get_multi_compartment(self, multi_uuid: str | int, parse: bool = False match resp.status: case 200: self._log.debug(f'parcel with multicompartment uuid {multi_uuid} received') - return await resp.json() if not parse else [Parcel(data, logger=self._log) for data in (await resp.json())['parcels']] + return await resp.json() if not parse else [Parcel(data, logger=self._log) for data in + (await resp.json())['parcels']] case 401: self._log.error(f'could not get parcel with multicompartment uuid {multi_uuid}, unauthorized') raise UnauthorizedError(reason=resp) @@ -669,3 +670,170 @@ async def get_prices(self) -> dict: self._log.error('could not get parcel prices, unhandled status') raise UnidentifiedAPIError(reason=resp) + + async def get_friends(self) -> dict: + """Fetches user friends for inpost services + + :return: :class:`dict` of user friends for inpost services + :rtype: dict + :raises NotAuthenticatedError: User not authenticated in inpost service + :raises UnauthorizedError: Unauthorized access to inpost services, + :raises NotFoundError: Phone number not found + :raises UnidentifiedAPIError: Unexpected thing happened""" + self._log.info(f'getting parcel prices') + + async with await self.sess.get(url=friends, + headers={'Authorization': self.auth_token}) as resp: + match resp.status: + case 200: + self._log.debug(f'got user friends') + return await resp.json() + case 401: + self._log.error('could not get user friends, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not get user friends, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not get user friends, unhandled status') + + raise UnidentifiedAPIError(reason=resp) + + async def add_friend(self, name: str, phone_number: str | int) -> dict: + """Adds user friends for inpost services + + :param name: name of further inpost friend + :type name: str + :param phone_number: further friend phone number + :type phone_number: str | int + :return: :class:`dict` with friends details + :rtype: dict + :raises NotAuthenticatedError: User not authenticated in inpost service + :raises UnauthorizedError: Unauthorized access to inpost services, + :raises NotFoundError: User with specified phone number not found + :raises UnidentifiedAPIError: Unexpected thing happened + :raises ValueError: Name length exceeds 20 characters""" + + self._log.info(f'adding user friend') + + if len(name) > 20: + raise ValueError(f'Name too long: {name} (over 20 characters') + + if isinstance(phone_number, int): + phone_number = str(phone_number) + + async with await self.sess.post(url=friends, + headers={'Authorization': self.auth_token}, + json={'phoneNumber': phone_number, + 'name': name}) as resp: + match resp.status: + case 200: + self._log.debug(f'added user friends') + friend = await resp.json() + return {'invitation_code': friend['invitationCode'], + 'expiry_date': friend['expiryDate']} + case 401: + self._log.error('could not add user friends, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not add user friends, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not add user friends, unhandled status') + + raise UnidentifiedAPIError(reason=resp) + + async def remove_friend(self, uuid: str | None, name: str | None, phone_number: str | int | None) -> bool: + """Removes user friend for inpost services with specified `uuid`/`phone_number`/`name` + + :param uuid: uuid of inpost friend to remove + :type uuid: str | None + :param name: name of inpost friend to remove + :type name: str | None + :param phone_number: phone number of inpost friend to remove + :type phone_number: str | int | None + :return: True if friend is removed + :rtype: bool + :raises NotAuthenticatedError: User not authenticated in inpost service + :raises UnauthorizedError: Unauthorized access to inpost services, + :raises NotFoundError: Friend not found + :raises UnidentifiedAPIError: Unexpected thing happened + :raises ValueError: Name length exceeds 20 characters""" + + self._log.info(f'adding user friend') + + if uuid is None and name is None and phone_number is None: + raise MissingParamsError(reason='None of params are filled (one required)') + + if isinstance(phone_number, int): + phone_number = str(phone_number) + + if uuid is None: + f = await self.get_friends() + if phone_number: + uuid = next((friend['uuid'] for friend in f if friend['phoneNumber'] == phone_number)) + else: + uuid = next((friend['uuid'] for friend in f if friend['name'] == name)) + + async with await self.sess.delete(url=f'{friends}{uuid}', + headers={'Authorization': self.auth_token}) as resp: + match resp.status: + case 200: + self._log.debug(f'removed user friend') + return True + case 401: + self._log.error('could not remove user friend, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not remove user friend, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not remove user friend, unhandled status') + + raise UnidentifiedAPIError(reason=resp) + + async def update_friend(self, uuid: str | None, phone_number: str | int | None, name: str): + """Updates user friend for inpost services with specified `name` + + :param uuid: uuid of inpost friend to update + :type uuid: str | None + :param name: new name of inpost friend + :type name: str + :param phone_number: phone number of inpost friend to update + :type phone_number: str | int | None + :return: True if friend is updated + :rtype: bool + :raises NotAuthenticatedError: User not authenticated in inpost service + :raises UnauthorizedError: Unauthorized access to inpost services, + :raises NotFoundError: Friend not found + :raises UnidentifiedAPIError: Unexpected thing happened + :raises ValueError: Name length exceeds 20 characters""" + + self._log.info(f'updating user friend') + + if len(name) > 20: + raise ValueError(f'Name too long: {name} (over 20 characters') + + if isinstance(phone_number, int): + phone_number = str(phone_number) + + if uuid is None: + uuid = next((friend['uuid'] for friend in await self.get_friends() if friend['phoneNumber'] == phone_number)) + + async with await self.sess.patch(url=f'{friends}{uuid}', + headers={'Authorization': self.auth_token}, + json={'name': name}) as resp: + match resp.status: + case 200: + self._log.debug(f'updated user friend') + return True + case 401: + self._log.error('could not update user friend, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not update user friend, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not update user friend, unhandled status') + + raise UnidentifiedAPIError(reason=resp) diff --git a/inpost/static/__init__.py b/inpost/static/__init__.py index 77d7bd1..4119b8f 100644 --- a/inpost/static/__init__.py +++ b/inpost/static/__init__.py @@ -6,7 +6,7 @@ ParcelServiceName, ParcelStatus from .exceptions import NoParcelError, UnidentifiedParcelError, ParcelTypeError, NotAuthenticatedError, ReAuthenticationError, \ PhoneNumberError, SmsCodeError, RefreshTokenError, UnidentifiedAPIError, UserLocationError, \ - UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError + UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError, MissingParamsError from .endpoints import login, send_sms_code, confirm_sms_code, refresh_token, parcels, parcel, collect, \ compartment_open, compartment_status, terminate_collect_session, friends, shared, sent, returns, parcel_prices, \ tickets, logout, multi diff --git a/inpost/static/exceptions.py b/inpost/static/exceptions.py index 3eb41d7..7319c79 100644 --- a/inpost/static/exceptions.py +++ b/inpost/static/exceptions.py @@ -89,6 +89,11 @@ class SingleParamError(BaseInpostError): pass +class MissingParamsError(BaseInpostError): + """Is raised when none of params are filled""" + pass + + class UnidentifiedError(BaseInpostError): """Is raised when no other error match""" pass From 132bf71b7d11a0443df948084d4cfa0e82fec150 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 9 Feb 2023 11:49:36 +0100 Subject: [PATCH 06/14] version 0.0.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 909eef0..1e18e4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "inpost" -version = "0.0.6" +version = "0.0.7" description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app" authors = ["loboda4450 ", "MrKazik99 "] maintainers = ["loboda4450 "] From 2460b14667212cbe7d88b125bc286f3df7a18fe0 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Thu, 9 Feb 2023 13:41:09 +0100 Subject: [PATCH 07/14] major improvement with friends --- inpost/api.py | 109 +++++++++++++++++++++++++++---------- inpost/static/__init__.py | 3 +- inpost/static/endpoints.py | 4 +- inpost/static/friends.py | 34 ++++++++++++ 4 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 inpost/static/friends.py diff --git a/inpost/api.py b/inpost/api.py index ca9aa35..73a348a 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -671,9 +671,11 @@ async def get_prices(self) -> dict: raise UnidentifiedAPIError(reason=resp) - async def get_friends(self) -> dict: + async def get_friends(self, parse=False) -> dict | List[Friend]: """Fetches user friends for inpost services + :param parse: switch for parsing response + :type parse: bool :return: :class:`dict` of user friends for inpost services :rtype: dict :raises NotAuthenticatedError: User not authenticated in inpost service @@ -687,7 +689,8 @@ async def get_friends(self) -> dict: match resp.status: case 200: self._log.debug(f'got user friends') - return await resp.json() + r = await resp.json() + return r if not parse else [Friend(friend_data=friend, logger=self._log) for friend in r['friends']] case 401: self._log.error('could not get user friends, unauthorized') raise UnauthorizedError(reason=resp) @@ -699,13 +702,17 @@ async def get_friends(self) -> dict: raise UnidentifiedAPIError(reason=resp) - async def add_friend(self, name: str, phone_number: str | int) -> dict: + async def add_friend(self, name: str, phone_number: str | int, code: str | int, parse=False) -> dict | Friend: """Adds user friends for inpost services :param name: name of further inpost friend :type name: str :param phone_number: further friend phone number :type phone_number: str | int + :param code: used when you have a friendship code from your friend + :type code: str | int + :param parse: switch for parsing response + :type parse: bool :return: :class:`dict` with friends details :rtype: dict :raises NotAuthenticatedError: User not authenticated in inpost service @@ -715,33 +722,78 @@ async def add_friend(self, name: str, phone_number: str | int) -> dict: :raises ValueError: Name length exceeds 20 characters""" self._log.info(f'adding user friend') - if len(name) > 20: raise ValueError(f'Name too long: {name} (over 20 characters') - if isinstance(phone_number, int): - phone_number = str(phone_number) - - async with await self.sess.post(url=friends, - headers={'Authorization': self.auth_token}, - json={'phoneNumber': phone_number, - 'name': name}) as resp: - match resp.status: - case 200: - self._log.debug(f'added user friends') - friend = await resp.json() - return {'invitation_code': friend['invitationCode'], - 'expiry_date': friend['expiryDate']} - case 401: - self._log.error('could not add user friends, unauthorized') - raise UnauthorizedError(reason=resp) - case 404: - self._log.error('could not add user friends, not found') - raise NotFoundError(reason=resp) - case _: - self._log.error('could not add user friends, unhandled status') - - raise UnidentifiedAPIError(reason=resp) + if code: + if isinstance(code, int): + code = str(code) + + async with await self.sess.post(url=validate_friendship, + headers={'Authorization': self.auth_token}, + json={'invitationCode': code}) as resp: + match resp.status: + case 200: + self._log.debug(f'validated friendship code') + async with await self.sess.post(url=accept_friendship, + headers={'Authorization': self.auth_token}, + json={'invitationCode': code, + 'friendName': name}): + match resp.status: + case 200: + self._log.debug(f'added user friend') + return await resp.json() if not parse else Friend(await resp.json(), logger=self._log) + + case 401: + self._log.error('could not add user friends, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not add user friends, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not add user friends, unhandled status') + case 401: + self._log.error('could not validate friendship code, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not validate friendship code, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not validate friendship code, unhandled status') + + raise UnidentifiedAPIError(reason=resp) + + else: + if isinstance(phone_number, int): + phone_number = str(phone_number) + + async with await self.sess.post(url=friends, + headers={'Authorization': self.auth_token}, + json={'phoneNumber': phone_number, + 'name': name}) as resp: + match resp.status: + case 200: + self._log.debug(f'added user friend') + r = await resp.json() + if r['status'] == "AUTO_ACCEPT": + return {'phoneNumber': phone_number, 'name': name} if not parse \ + else Friend({'phoneNumber': phone_number, 'name': name}, logger=self._log) + + elif r['status'] == "RETURN_INVITATION_CODE": + return r if not parse else Friend.from_invitation(invitation_data=r, logger=self._log) + + else: + ... + case 401: + self._log.error('could not add user friends, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not add user friends, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not add user friends, unhandled status') + + raise UnidentifiedAPIError(reason=resp) async def remove_friend(self, uuid: str | None, name: str | None, phone_number: str | int | None) -> bool: """Removes user friend for inpost services with specified `uuid`/`phone_number`/`name` @@ -818,7 +870,8 @@ async def update_friend(self, uuid: str | None, phone_number: str | int | None, phone_number = str(phone_number) if uuid is None: - uuid = next((friend['uuid'] for friend in await self.get_friends() if friend['phoneNumber'] == phone_number)) + uuid = next( + (friend['uuid'] for friend in await self.get_friends() if friend['phoneNumber'] == phone_number)) async with await self.sess.patch(url=f'{friends}{uuid}', headers={'Authorization': self.auth_token}, diff --git a/inpost/static/__init__.py b/inpost/static/__init__.py index 4119b8f..ac6f8c7 100644 --- a/inpost/static/__init__.py +++ b/inpost/static/__init__.py @@ -9,4 +9,5 @@ UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError, MissingParamsError from .endpoints import login, send_sms_code, confirm_sms_code, refresh_token, parcels, parcel, collect, \ compartment_open, compartment_status, terminate_collect_session, friends, shared, sent, returns, parcel_prices, \ - tickets, logout, multi + tickets, logout, multi, validate_friendship, accept_friendship +from .friends import Friend diff --git a/inpost/static/endpoints.py b/inpost/static/endpoints.py index 5a2599a..0b1bb84 100644 --- a/inpost/static/endpoints.py +++ b/inpost/static/endpoints.py @@ -12,7 +12,9 @@ compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post terminate_collect_session: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/terminate' # post -friends: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get +friends: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get, post, patch, delete +validate_friendship: str = 'https://api-inmobile-pl.easypack24.net/v1/invitations/validate' # post +accept_friendship: str = 'https://api-inmobile-pl.easypack24.net/v1/invitations/accept' # post shared: str = 'https://api-inmobile-pl.easypack24.net/v1/parcels/shared' # post sent: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/sent/' # get returns: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/parcels/' # get diff --git a/inpost/static/friends.py b/inpost/static/friends.py new file mode 100644 index 0000000..026921a --- /dev/null +++ b/inpost/static/friends.py @@ -0,0 +1,34 @@ +import logging + +from arrow import get, Arrow + + +class Friend: + def __init__(self, friend_data, logger: logging.Logger): + self.uuid: str = friend_data['uuid'] if 'uuid' in friend_data else None + self.phone_number: str = friend_data['phoneNumber'] + self.name: str = friend_data['name'] + self._log: logging.Logger = logger.getChild(f'{__class__.__name__}.{self.uuid}') + self.invitaion_code: str | None = friend_data['invitationCode'] if 'invitationCode' in friend_data else None + self.created_date: Arrow | None = get(friend_data['createdDate']) if 'createdDate' in friend_data else None + self.expiry_date: Arrow | None = get(friend_data['expiryDate']) if 'expiryDate' in friend_data else None + + if self.invitaion_code: + self._log.debug(f'created friendship with {self.name} using from_invitation') + else: + self._log.debug(f'created friendship with {self.name}') + + @classmethod + def from_invitation(cls, invitation_data, logger: logging.Logger): + return cls(friend_data={'uuid': invitation_data['friend']['uuid'], + 'phoneNumber': invitation_data['friend']['phoneNumber'], + 'name': invitation_data['friend']['name'], + 'invitationCode': invitation_data['invitationCode'], + 'createdDate': invitation_data['createdDate'], + 'expiryDate': invitation_data['expiryDate'] + }, + logger=logger) + + def __repr__(self): + fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') + return self.__class__.__name__ + str(tuple(sorted(fields))).replace("\'", "") From 14ad98b808ac4453823ab5244a32a5664c19dea9 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sat, 4 Mar 2023 13:24:21 +0100 Subject: [PATCH 08/14] new translations --- inpost/static/statuses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inpost/static/statuses.py b/inpost/static/statuses.py index 0d99d2e..795b4f1 100644 --- a/inpost/static/statuses.py +++ b/inpost/static/statuses.py @@ -143,12 +143,12 @@ class ParcelStatus(ParcelBase): DELIVERED = 'Doręczona' CANCELED = 'Anulowana' # TODO: translate from app CLAIMED = 'Zareklamowana' - STACK_IN_CUSTOMER_SERVICE_POINT = 'Umieszczona w punkcie obsługi klienta' # TODO: translate from app + STACK_IN_CUSTOMER_SERVICE_POINT = 'Przesyłka magazynowana 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 COURIER_AVIZO_IN_CUSTOMER_SERVICE_POINT = 'Przekazana do punktu obsługi klienta' # TODO: translate from app TAKEN_BY_COURIER_FROM_CUSTOMER_SERVICE_POINT = 'Odebrana przez kuriera z punktu obsługi klienta' # TODO: translate from app - STACK_IN_BOX_MACHINE = 'Paczka w paczkomacie' # TODO: translate from app + STACK_IN_BOX_MACHINE = 'Przesyłka magazynowana w paczkomacie tymczasowym' STACK_PARCEL_IN_BOX_MACHINE_PICKUP_TIME_EXPIRED = 'Upłynął czas odbioru z paczkomatu' # TODO: translate from app UNSTACK_FROM_BOX_MACHINE = 'Odebrana z paczkomatu' # TODO: translate from app ADOPTED_AT_SORTING_CENTER = 'Przyjęta w sortowni' From c1560578ace3742bd7f5d1173f60a01c2eef91b6 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Sat, 4 Mar 2023 15:12:25 +0100 Subject: [PATCH 09/14] cleanup, better friends support, scratch of sharing parcel --- inpost/api.py | 112 ++++++++++++++++++++++++++++++++++--- inpost/static/__init__.py | 2 +- inpost/static/endpoints.py | 2 +- 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/inpost/api.py b/inpost/api.py index 73a348a..3b35c37 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -682,9 +682,13 @@ async def get_friends(self, parse=False) -> dict | List[Friend]: :raises UnauthorizedError: Unauthorized access to inpost services, :raises NotFoundError: Phone number not found :raises UnidentifiedAPIError: Unexpected thing happened""" - self._log.info(f'getting parcel prices') + self._log.info(f'getting friends') + + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') - async with await self.sess.get(url=friends, + async with await self.sess.get(url=friendship, headers={'Authorization': self.auth_token}) as resp: match resp.status: case 200: @@ -702,6 +706,38 @@ async def get_friends(self, parse=False) -> dict | List[Friend]: raise UnidentifiedAPIError(reason=resp) + async def get_parcel_friends(self, shipment_number: int | str, parse=False) -> dict: + self._log.info(f'getting parcel friends') + + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.get(url=f"{friendship}{shipment_number}", + headers={'Authorization': self.auth_token}) as resp: + match resp.status: + case 200: + self._log.debug(f'got user friends') + r = await resp.json() + if 'sharedWith' in r: + return r if not parse else { + 'friends': [Friend(friend_data=friend, logger=self._log) for friend in r['friends']], + 'shared_with': [Friend(friend_data=friend, logger=self._log) for friend in r['sharedWith']]} + return r if not parse else { + 'friends': [Friend(friend_data=friend, logger=self._log) for friend in r['friends']] + } + + case 401: + self._log.error('could not get user friends, unauthorized') + raise UnauthorizedError(reason=resp) + case 404: + self._log.error('could not get user friends, not found') + raise NotFoundError(reason=resp) + case _: + self._log.error('could not get user friends, unhandled status') + + raise UnidentifiedAPIError(reason=resp) + async def add_friend(self, name: str, phone_number: str | int, code: str | int, parse=False) -> dict | Friend: """Adds user friends for inpost services @@ -722,6 +758,11 @@ async def add_friend(self, name: str, phone_number: str | int, code: str | int, :raises ValueError: Name length exceeds 20 characters""" self._log.info(f'adding user friend') + + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + if len(name) > 20: raise ValueError(f'Name too long: {name} (over 20 characters') @@ -742,7 +783,8 @@ async def add_friend(self, name: str, phone_number: str | int, code: str | int, match resp.status: case 200: self._log.debug(f'added user friend') - return await resp.json() if not parse else Friend(await resp.json(), logger=self._log) + return await resp.json() if not parse else Friend(await resp.json(), + logger=self._log) case 401: self._log.error('could not add user friends, unauthorized') @@ -767,7 +809,7 @@ async def add_friend(self, name: str, phone_number: str | int, code: str | int, if isinstance(phone_number, int): phone_number = str(phone_number) - async with await self.sess.post(url=friends, + async with await self.sess.post(url=friendship, headers={'Authorization': self.auth_token}, json={'phoneNumber': phone_number, 'name': name}) as resp: @@ -814,6 +856,10 @@ async def remove_friend(self, uuid: str | None, name: str | None, phone_number: self._log.info(f'adding user friend') + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + if uuid is None and name is None and phone_number is None: raise MissingParamsError(reason='None of params are filled (one required)') @@ -823,11 +869,11 @@ async def remove_friend(self, uuid: str | None, name: str | None, phone_number: if uuid is None: f = await self.get_friends() if phone_number: - uuid = next((friend['uuid'] for friend in f if friend['phoneNumber'] == phone_number)) + uuid = next((friend['uuid'] for friend in f['friends'] if friend['phoneNumber'] == phone_number)) else: - uuid = next((friend['uuid'] for friend in f if friend['name'] == name)) + uuid = next((friend['uuid'] for friend in f['friends'] if friend['name'] == name)) - async with await self.sess.delete(url=f'{friends}{uuid}', + async with await self.sess.delete(url=f'{friendship}{uuid}', headers={'Authorization': self.auth_token}) as resp: match resp.status: case 200: @@ -863,6 +909,10 @@ async def update_friend(self, uuid: str | None, phone_number: str | int | None, self._log.info(f'updating user friend') + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + if len(name) > 20: raise ValueError(f'Name too long: {name} (over 20 characters') @@ -871,7 +921,7 @@ async def update_friend(self, uuid: str | None, phone_number: str | int | None, if uuid is None: uuid = next( - (friend['uuid'] for friend in await self.get_friends() if friend['phoneNumber'] == phone_number)) + (friend['uuid'] for friend in (await self.get_friends())['friends'] if friend['phoneNumber'] == phone_number)) async with await self.sess.patch(url=f'{friends}{uuid}', headers={'Authorization': self.auth_token}, @@ -890,3 +940,49 @@ async def update_friend(self, uuid: str | None, phone_number: str | int | None, self._log.error('could not update user friend, unhandled status') raise UnidentifiedAPIError(reason=resp) + + async def share_parcel(self, uuid: str, shipment_number: int | str): + """Shares parcel to a pre-initialized friend + + :param uuid: uuid of inpost friend to update + :type uuid: str + :param shipment_number: Parcel's shipment number + :type shipment_number: int | str + :return: True if parcel is shared + :rtype: bool + :raises NotAuthenticatedError: User not authenticated in inpost service + :raises UnauthorizedError: Unauthorized access to inpost services, + :raises NotFoundError: Parcel or friend not found + :raises UnidentifiedAPIError: Unexpected thing happened""" + + self._log.info(f'sharing parcel: {shipment_number}') + + if not self.auth_token: + self._log.debug(f'authorization token missing') + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.post(url=shared, + json={ + 'parcels': [ + { + 'shipmentNumber': shipment_number, + 'friendUuids': [ + uuid + ] + } + ], + }) as share: + match share.status: + case 200: + self._log.debug(f'shared parcel: {shipment_number}') + return True + case 401: + self._log.error(f'could not share parcel: {shipment_number}, unauthorized') + raise UnauthorizedError(reason=share) + case 404: + self._log.error(f'could not share parcel: {shipment_number}, not found') + raise NotFoundError(reason=share) + case _: + self._log.error(f'could not share parcel: {shipment_number}, unhandled status') + + raise UnidentifiedAPIError(reason=share) diff --git a/inpost/static/__init__.py b/inpost/static/__init__.py index ac6f8c7..0176eca 100644 --- a/inpost/static/__init__.py +++ b/inpost/static/__init__.py @@ -8,6 +8,6 @@ PhoneNumberError, SmsCodeError, RefreshTokenError, UnidentifiedAPIError, UserLocationError, \ UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError, MissingParamsError from .endpoints import login, send_sms_code, confirm_sms_code, refresh_token, parcels, parcel, collect, \ - compartment_open, compartment_status, terminate_collect_session, friends, shared, sent, returns, parcel_prices, \ + compartment_open, compartment_status, terminate_collect_session, friendship, shared, sent, returns, parcel_prices, \ tickets, logout, multi, validate_friendship, accept_friendship from .friends import Friend diff --git a/inpost/static/endpoints.py b/inpost/static/endpoints.py index 0b1bb84..2f2af3f 100644 --- a/inpost/static/endpoints.py +++ b/inpost/static/endpoints.py @@ -12,7 +12,7 @@ compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post terminate_collect_session: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/terminate' # post -friends: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get, post, patch, delete +friendship: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get, post, patch, delete validate_friendship: str = 'https://api-inmobile-pl.easypack24.net/v1/invitations/validate' # post accept_friendship: str = 'https://api-inmobile-pl.easypack24.net/v1/invitations/accept' # post shared: str = 'https://api-inmobile-pl.easypack24.net/v1/parcels/shared' # post From bb6b84a8e716b12708183e65eee8075ed6f9b64b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 14 Apr 2023 10:25:00 +0200 Subject: [PATCH 10/14] outgoing returns handled --- inpost/static/__init__.py | 2 +- inpost/static/parcels.py | 51 ++++++++++++++++++++++++++++++++------- inpost/static/statuses.py | 7 ++++++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/inpost/static/__init__.py b/inpost/static/__init__.py index 0176eca..5dfa7fd 100644 --- a/inpost/static/__init__.py +++ b/inpost/static/__init__.py @@ -3,7 +3,7 @@ from .headers import appjson from .statuses import ParcelCarrierSize, ParcelLockerSize, ParcelDeliveryType, ParcelShipmentType, \ ParcelAdditionalInsurance, ParcelType, ParcelOwnership, CompartmentExpectedStatus, CompartmentActualStatus, \ - ParcelServiceName, ParcelStatus + ParcelServiceName, ParcelStatus, ReturnsStatus from .exceptions import NoParcelError, UnidentifiedParcelError, ParcelTypeError, NotAuthenticatedError, ReAuthenticationError, \ PhoneNumberError, SmsCodeError, RefreshTokenError, UnidentifiedAPIError, UserLocationError, \ UnidentifiedError, NotFoundError, UnauthorizedError, SingleParamError, MissingParamsError diff --git a/inpost/static/parcels.py b/inpost/static/parcels.py index a0bb70f..56695da 100644 --- a/inpost/static/parcels.py +++ b/inpost/static/parcels.py @@ -9,7 +9,20 @@ from inpost.static.statuses import * -class Parcel: +class BaseParcel: + def __init__(self, parcel_data: dict, logger: logging.Logger): + self.shipment_number: str = parcel_data['shipmentNumber'] + self._log: logging.Logger = logger.getChild(f'{__class__.__name__}.{self.shipment_number}') + self.status: ParcelStatus = ParcelStatus[parcel_data['status']] + # self.parcel_size: ParcelLockerSize | ParcelCarrierSize = ParcelLockerSize[parcel_data['parcelSize']] \ + # if self.shipment_type == ParcelShipmentType.parcel else ParcelCarrierSize[parcel_data['parcelSize']] + self.expiry_date: arrow | None = get(parcel_data['expiryDate']) if 'expiryDate' in parcel_data else None + self.operations: Operations = Operations(operations_data=parcel_data['operations'], logger=self._log) + self.event_log: List[EventLog] = [EventLog(eventlog_data=event, logger=self._log) + for event in parcel_data['eventLog']] + + +class Parcel(BaseParcel): """Object representation of :class:`inpost.api.Inpost` compartment properties :param parcel_data: :class:`dict` containing all parcel data @@ -19,7 +32,8 @@ class Parcel: def __init__(self, parcel_data: dict, logger: logging.Logger): """Constructor method""" - self.shipment_number: str = parcel_data['shipmentNumber'] + super().__init__(parcel_data, logger) + # self.shipment_number: str = parcel_data['shipmentNumber'] self._log: logging.Logger = logger.getChild(f'{__class__.__name__}.{self.shipment_number}') self.shipment_type: ParcelShipmentType = ParcelShipmentType[parcel_data['shipmentType']] self._open_code: str | None = parcel_data['openCode'] if 'openCode' in parcel_data else None @@ -27,7 +41,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.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) @@ -37,10 +51,10 @@ def __init__(self, parcel_data: dict, logger: logging.Logger): self.multi_compartment: MultiCompartment | None = MultiCompartment( parcel_data['multiCompartment'], logger=self._log) if 'multiCompartment' in parcel_data else None self.is_end_off_week_collection: bool = parcel_data['endOfWeekCollection'] - self.operations: Operations = Operations(operations_data=parcel_data['operations'], logger=self._log) + # self.operations: Operations = Operations(operations_data=parcel_data['operations'], logger=self._log) self.status: ParcelStatus = ParcelStatus[parcel_data['status']] - self.event_log: List[EventLog] = [EventLog(eventlog_data=event, logger=self._log) - for event in parcel_data['eventLog']] + # self.event_log: List[EventLog] = [EventLog(eventlog_data=event, logger=self._log) + # for event in parcel_data['eventLog']] self.avizo_transaction_status: str = parcel_data['avizoTransactionStatus'] self.shared_to: List[SharedTo] = [SharedTo(sharedto_data=person, logger=self._log) for person in parcel_data['sharedTo']] @@ -236,6 +250,24 @@ def is_main_multicompartment(self): # return +class ReturnParcel(BaseParcel): + def __init__(self, parcel_data: dict, logger: logging.Logger): + super().__init__(parcel_data, logger) + self.uuid: str = parcel_data['uuid'] + self.rma: str = parcel_data['rma'] + self.organization_name: str = parcel_data['organizationName'] + self.created_date: arrow = parcel_data['createdDate'] + self.accepted_date: arrow = parcel_data['acceptedDate'] + self.expiry_date: arrow = parcel_data['expiryDate'] + self.sent_date: arrow = parcel_data['sentDate'] + self.delivered_date: arrow = parcel_data['deliveredDate'] + self.order_number: str = parcel_data['orderNumber'] + self.form_type: str = parcel_data['formType'] + + + + + class Receiver: """Object representation of :class:`Parcel` receiver @@ -380,6 +412,7 @@ def __init__(self, operations_data: dict, logger: logging.Logger): self.can_share_to_observe: bool = operations_data['canShareToObserve'] self.can_share_open_code: bool = operations_data['canShareOpenCode'] self.can_share_parcel: bool = operations_data['canShareParcel'] + self.send: bool = operations_data['send'] self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') @@ -400,14 +433,14 @@ class EventLog: def __init__(self, eventlog_data: dict, logger: logging.Logger): """Constructor method""" self.type: str = eventlog_data['type'] - self.name: ParcelStatus = ParcelStatus[eventlog_data['name']] + self.name: ParcelStatus | ReturnsStatus = ParcelStatus[eventlog_data['name']] if self.type == 'PARCEL_STATUS' else ReturnsStatus[eventlog_data['name']] self.date: arrow = get(eventlog_data['date']) self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') - if self.name == ParcelStatus.UNKNOWN: - self._log.debug(f'unknown parcel status: {eventlog_data["name"]}') + if self.name == ParcelStatus.UNKNOWN or self.name == ReturnsStatus.UNKNOWN: + self._log.debug(f'unknown {self.type}: {eventlog_data["name"]}') def __repr__(self): fields = tuple(f"{k}={v}" for k, v in self.__dict__.items() if k != '_log') diff --git a/inpost/static/statuses.py b/inpost/static/statuses.py index 795b4f1..5ce91ce 100644 --- a/inpost/static/statuses.py +++ b/inpost/static/statuses.py @@ -160,6 +160,13 @@ class ParcelStatus(ParcelBase): CANCELED_REDIRECT_TO_BOX = 'Anulowano przekierowanie do paczkomatu' # TODO: translate from app +class ReturnsStatus(ParcelBase): # TODO: translate from app and fill missing ones + ACCEPTED = 'Zaakceptowano' + USED = 'Nadano' + DELIVERED = 'Dostarczono' + UNKNOWN = 'UNKNOWN DATA' + + class ParcelOwnership(ParcelBase): """:class:`Enum` that holds parcel ownership types""" UNKNOWN = 'UNKNOWN DATA' From 1a2e6722befaca6f59418e16561d5263f32b6902 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 14 Apr 2023 11:59:02 +0200 Subject: [PATCH 11/14] missing header in sharing parcel --- inpost/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/inpost/api.py b/inpost/api.py index 3b35c37..6f321f2 100644 --- a/inpost/api.py +++ b/inpost/api.py @@ -962,6 +962,7 @@ async def share_parcel(self, uuid: str, shipment_number: int | str): raise NotAuthenticatedError(reason='Not logged in') async with await self.sess.post(url=shared, + headers={'Authorization': self.auth_token}, json={ 'parcels': [ { From 6064372adad8bcb13415d6df40ef52aab0993d27 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 14 Apr 2023 11:59:24 +0200 Subject: [PATCH 12/14] version 0.0.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1e18e4f..547f52c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "inpost" -version = "0.0.7" +version = "0.0.8" description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app" authors = ["loboda4450 ", "MrKazik99 "] maintainers = ["loboda4450 "] From 2a893dba3a27a8e93f4bc9ef97e948fb2aee6e7b Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 14 Apr 2023 12:17:32 +0200 Subject: [PATCH 13/14] Operations fix --- inpost/static/parcels.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/inpost/static/parcels.py b/inpost/static/parcels.py index 56695da..687ca79 100644 --- a/inpost/static/parcels.py +++ b/inpost/static/parcels.py @@ -265,9 +265,6 @@ def __init__(self, parcel_data: dict, logger: logging.Logger): self.form_type: str = parcel_data['formType'] - - - class Receiver: """Object representation of :class:`Parcel` receiver @@ -412,7 +409,7 @@ def __init__(self, operations_data: dict, logger: logging.Logger): self.can_share_to_observe: bool = operations_data['canShareToObserve'] self.can_share_open_code: bool = operations_data['canShareOpenCode'] self.can_share_parcel: bool = operations_data['canShareParcel'] - self.send: bool = operations_data['send'] + self.send: bool | None = operations_data['send'] if 'send' in operations_data else None self._log: logging.Logger = logger.getChild(__class__.__name__) self._log.debug('created') @@ -433,7 +430,8 @@ class EventLog: def __init__(self, eventlog_data: dict, logger: logging.Logger): """Constructor method""" self.type: str = eventlog_data['type'] - self.name: ParcelStatus | ReturnsStatus = ParcelStatus[eventlog_data['name']] if self.type == 'PARCEL_STATUS' else ReturnsStatus[eventlog_data['name']] + self.name: ParcelStatus | ReturnsStatus = ParcelStatus[ + eventlog_data['name']] if self.type == 'PARCEL_STATUS' else ReturnsStatus[eventlog_data['name']] self.date: arrow = get(eventlog_data['date']) self._log: logging.Logger = logger.getChild(__class__.__name__) From c71c820497ff21a459d53913f740dbea78f25e41 Mon Sep 17 00:00:00 2001 From: loboda4450 Date: Fri, 14 Apr 2023 12:19:45 +0200 Subject: [PATCH 14/14] version 0.0.9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 547f52c..d63b585 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "inpost" -version = "0.0.8" +version = "0.0.9" description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app" authors = ["loboda4450 ", "MrKazik99 "] maintainers = ["loboda4450 "]