Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
max-line-length = 120
max-complexity = 12
select = B,C,E,F,W,T4,B9
ignore = E501, E731, W503, F401, F403
ignore = E501, E731, W503, F401, F403, E121, E127
exclude = docs
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
repos:
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.3.0
rev: 24.4.0
hooks:
- id: black
exclude: ^docs/
language_version: python3
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 7.0.0
hooks:
- id: flake8
exclude: ^docs/
Expand All @@ -19,7 +19,7 @@ repos:
- flake8-comprehensions
- flake8-pytest
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
rev: v1.9.0
hooks:
- id: mypy
exclude: ^docs/
Expand All @@ -29,7 +29,7 @@ repos:
- id: darglint
exclude: ^docs/
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.3.0
rev: v3.22.0
hooks:
- id: commitizen
stages: [commit-msg]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

Fully async Inpost library using Python 3.10.

# WARNING
Please be aware, that version 0.2.x is not compatible with 0.1.x builds! Thank inpost for making their api upside down >:(


## Documentation

Expand Down
96 changes: 56 additions & 40 deletions inpost/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,62 @@
validate_friendship_url,
validate_sent_url,
)
from inpost.static.headers import useragent


class Inpost:
"""Python representation of an Inpost app. Essentially implements methods to manage all incoming parcels"""

def __init__(self, phone_number):
def __init__(
self,
prefix: str,
phone_number: str,
sms_code=None,
auth_token=None,
refr_token=None,
):
"""Constructor method

:param prefix: country code
:type prefix: str
:param phone_number: phone number
:type phone_number: str
:param sms_code: sms code from inpost
:type sms_code: str | None
:param auth_token: authorization token from inpost
:type auth_token: str
:param refr_token: refresh token from inpost
:type refr_token: str
:raises PhoneNumberError: Wrong phone number format or is not digit
"""

if isinstance(phone_number, int):
phone_number = str(phone_number)

if not (len(phone_number) == 9 and phone_number.isdigit()):
raise PhoneNumberError(f"Wrong phone number format: {phone_number} (should be 9 digits)")

self.phone_number: str = phone_number
self.sms_code: str | None = None
self.auth_token: str | None = None
self.refr_token: str | None = None
if not (len(phone_number) == 9 and phone_number.isdigit()) or not (
prefix.startswith("+") and prefix[1:].isdigit() and 2 <= len(prefix) <= 3
):
raise PhoneNumberError(
f"Wrong phone number format: {phone_number} " f"(should be +xxx123123123 or +xx123123123)"
)
self.prefix: str = prefix
self.phone_number: str = phone_number[-9:]
self.sms_code: str | None = sms_code
self.auth_token: str | None = auth_token
self.refr_token: str | None = refr_token
self.sess: ClientSession = ClientSession()
self._log = logging.getLogger(f"{self.__class__.__name__}.{phone_number}")

self._log.setLevel(level=logging.DEBUG)
self._log.info(f"initialized inpost object with phone number {phone_number}")

@property
def combined_phone_number(self) -> str:
return self.prefix + self.phone_number

@property
def login_auth_data(self) -> dict:
return {"phoneNumber": {"prefix": self.prefix, "value": self.phone_number}}

def __repr__(self):
return f"{self.__class__.__name__}(phone_number={self.phone_number})"

Expand Down Expand Up @@ -142,6 +169,9 @@ async def request(
headers_ = {} if headers is None else headers

if auth:
if self.auth_token is None:
raise UnauthorizedError("Missing authorization token")

headers_.update({"Authorization": self.auth_token})

resp = await self.sess.request(method, url, headers=headers_, params=params, json=data, **kwargs)
Expand All @@ -166,25 +196,6 @@ async def request(

raise UnidentifiedAPIError(reason=resp)

@classmethod
def from_dict(cls, data: dict) -> "Inpost":
"""`Classmethod` to initialize :class:`Inpost` object with dict.
Should be used when retrieving configuration from database.

:param data: User's Inpost data (e.g. phone_number, sms_code, auth_token, refr_token)
:type data: dict
:return: Inpost object from provided dict
:rtype: Inpost
"""

inp = cls(phone_number=data["phone_number"])
inp.sms_code = data["sms_code"]
inp.auth_token = data["auth_token"]
inp.refr_token = data["refr_token"]

inp._log.info("initialized by from_dict")
return inp

async def send_sms_code(self) -> bool:
"""Sends sms code to `Inpost.phone_number`

Expand All @@ -203,8 +214,8 @@ async def send_sms_code(self) -> bool:
action="send sms code",
url=send_sms_code_url,
auth=False,
headers=None,
data={"phoneNumber": f"{self.phone_number}"},
headers=useragent | appjson,
data=self.login_auth_data,
autorefresh=False,
)

Expand All @@ -231,14 +242,13 @@ async def confirm_sms_code(self, sms_code: str | int) -> bool:
raise SmsCodeError(reason=f"Wrong sms code format: {sms_code} (should be 6 digits)")

self._log.info("confirming sms code")

resp = await self.request(
method="post",
action="confirm sms code",
url=confirm_sms_code_url,
auth=False,
headers=appjson,
data={"phoneNumber": self.phone_number, "smsCode": sms_code, "phoneOS": "Android"},
data={"smsCode": sms_code, "devicePlatform": "Android"} | self.login_auth_data,
autorefresh=False,
)

Expand Down Expand Up @@ -386,7 +396,7 @@ async def get_parcel(
resp = await self.request(
method="get",
action=f"parcel with shipment number {shipment_number}",
url=f"{url}{shipment_number}",
url=f"{url}/{shipment_number}",
auth=True,
headers=None,
data=None,
Expand Down Expand Up @@ -511,7 +521,7 @@ async def get_multi_compartment(self, multi_uuid: str | int, parse: bool = False
resp = await self.request(
method="get",
action=f"parcel with multi-compartment uuid {multi_uuid}",
url=f"{multi_url}{multi_uuid}",
url=f"{multi_url}/{multi_uuid}",
auth=True,
headers=None,
data=None,
Expand Down Expand Up @@ -569,17 +579,22 @@ async def collect_compartment_properties(
raise NoParcelError(reason="Could not obtain desired parcel!")

self._log.info(f"collecting compartment properties for {parcel_obj_.shipment_number}")

resp = await self.request(
method="post",
action="collect compartment properties",
url=collect_url,
auth=True,
headers=None,
data={
"parcel": parcel_obj_.compartment_open_data,
"geoPoint": location if location is not None else parcel_obj_.mocked_location,
},
# fmt: off
data={"parcel": parcel_obj_.compartment_open_data | {"receiverPhoneNumber":
{
"prefix": self.prefix,
"value": self.phone_number
}
},
"geoPoint": location if location is not None else parcel_obj_.mocked_location,
},
# fmt: on
autorefresh=True,
)

Expand Down Expand Up @@ -665,7 +680,8 @@ async def check_compartment_status(
if resp.status == 200:
self._log.debug(f"checked compartment status for {parcel_obj.shipment_number}")
parcel_obj.compartment_status = (await resp.json())["status"]
return CompartmentExpectedStatus[(await resp.json())["status"]] == expected_status
# return CompartmentExpectedStatus[(await resp.json())["status"]] == expected_status
return True

raise UnidentifiedAPIError(reason=resp)

Expand Down
12 changes: 6 additions & 6 deletions inpost/static/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# AUTH #
login_url: str = "https://api-inmobile-pl.easypack24.net/v1/authenticate"
send_sms_code_url: str = "https://api-inmobile-pl.easypack24.net/v1/sendSMSCode/" # get
confirm_sms_code_url: str = "https://api-inmobile-pl.easypack24.net/v1/confirmSMSCode" # post
send_sms_code_url: str = "https://api-inmobile-pl.easypack24.net/v1/account" # post
confirm_sms_code_url: str = "https://api-inmobile-pl.easypack24.net/v1/account/verification" # post
logout_url: str = "https://api-inmobile-pl.easypack24.net/v1/logout" # post
refresh_token_url: str = "https://api-inmobile-pl.easypack24.net/v1/authenticate" # post

# INCOMING PARCELS #
tracked_url: str = "https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/" # get
multi_url: str = "https://api-inmobile-pl.easypack24.net/v3/parcels/multi/" # get
collect_url: str = "https://api-inmobile-pl.easypack24.net/v1/collect/validate" # post
tracked_url: str = "https://api-inmobile-pl.easypack24.net/v4/parcels/tracked" # get
multi_url: str = "https://api-inmobile-pl.easypack24.net/v4/parcels/multi" # get
collect_url: str = "https://api-inmobile-pl.easypack24.net/v2/collect/validate" # post
compartment_reopen_url: str = "https://api-inmobile-pl.easypack24.net/v1/collect/compartment/reopen" # post
compartment_open_url: str = "https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open" # post
compartment_status_url: str = "https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status" # post
terminate_collect_session_url: str = "https://api-inmobile-pl.easypack24.net/v1/collect/terminate" # post
shared_url: str = "https://api-inmobile-pl.easypack24.net/v1/parcels/shared" # post
shared_url: str = "https://api-inmobile-pl.easypack24.net/v4/parcels/shared" # post

# CREATING PARCEL #
create_url: str = "https://api-inmobile-pl.easypack24.net/v1/parcels"
Expand Down
3 changes: 2 additions & 1 deletion inpost/static/headers.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
appjson = {"Content-Type": "application/json"}
appjson = {"Content-Type": "application/json; charset=UTF-8"}
useragent = {"User-Agent": "InPost-Mobile/3.23.0(32300001) (Android 9; unknown; unknown unknown; en)"}
7 changes: 3 additions & 4 deletions inpost/static/parcels.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def compartment_status(self, status) -> None:
self._log.warning(f"wrong ParcelShipmentType: {repr(self.shipment_type)}")

@property
def compartment_open_data(self) -> dict | None:
def compartment_open_data(self) -> dict:
"""Returns a compartment open data for :class:`Parcel`

:return: dict containing compartment open data for :class:`Parcel`
Expand All @@ -275,18 +275,17 @@ def compartment_open_data(self) -> dict | None:

self._log.debug("getting compartment open data")
if self.receiver is None:
return None
return {}

if self.shipment_type == ParcelShipmentType.parcel:
self._log.debug("got compartment open data")
return {
"shipmentNumber": self.shipment_number,
"openCode": self._open_code,
"receiverPhoneNumber": self.receiver.phone_number,
}

self._log.warning(f"wrong ParcelShipmentType: {repr(self.shipment_type)}")
return None
return {}

@property
def mocked_location(self) -> dict | None:
Expand Down
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "inpost"
version = "0.1.5"
version = "0.2.0a2"
description = "Asynchronous InPost package allowing you to manage existing incoming parcels without mobile app"
authors = ["loboda4450 <[email protected]>", "MrKazik99 <[email protected]>"]
maintainers = ["loboda4450 <[email protected]>"]
Expand All @@ -23,16 +23,15 @@ classifiers = [
]

[tool.poetry.dev-dependencies]
pre-commit = "^3.3.3"
pytest = "^7.4.0"
pre-commit = "^3.7.0"
pytest = "^8.1.1"

[tool.poetry.dependencies]
python = "^3.10"
aiohttp = "^3.8.1"
aiohttp = "^3.9.4"
arrow = "^1.2.3"
qrcode = "^7.3.1"
Pillow = "^9.4.0"
pytest = "^7.4.0"


[build-system]
Expand Down