2025-01-22 14:58:55 +00:00
|
|
|
import dataclasses
|
2025-01-22 16:31:00 +00:00
|
|
|
import datetime
|
2025-01-22 13:02:02 +00:00
|
|
|
import logging
|
2025-01-22 16:31:00 +00:00
|
|
|
import time
|
2025-01-22 14:58:55 +00:00
|
|
|
from collections.abc import Iterator
|
2025-01-22 13:02:02 +00:00
|
|
|
|
2025-01-22 16:31:00 +00:00
|
|
|
import requests
|
|
|
|
|
2025-01-22 13:02:02 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
URL_TRACKING = 'https://parcelsapp.com/api/v3/shipments/tracking'
|
|
|
|
|
|
|
|
target_country = 'Denmark'
|
|
|
|
|
|
|
|
TRACKING_STATUS_CHECKING_INTERVAL = 1
|
|
|
|
|
2025-01-22 16:31:00 +00:00
|
|
|
|
2025-01-22 14:58:55 +00:00
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
|
|
class ParcelState:
|
|
|
|
date: datetime.datetime
|
|
|
|
status: str
|
|
|
|
carrier: str | None
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
|
|
class ParcelInfo:
|
|
|
|
tracking_number: str
|
|
|
|
tracking_url: str
|
|
|
|
status: str
|
|
|
|
destination: str | None
|
|
|
|
origin: str | None
|
|
|
|
states: list[ParcelState]
|
|
|
|
# TODO: More fields
|
|
|
|
|
|
|
|
def latest_state(self) -> ParcelState:
|
|
|
|
return max(self.states, key=lambda state: state.date)
|
|
|
|
|
2025-01-26 18:59:27 +00:00
|
|
|
class ParcelsApiError(RuntimeError):
|
|
|
|
pass
|
2025-01-22 13:02:02 +00:00
|
|
|
|
2025-01-22 16:31:00 +00:00
|
|
|
class ParcelsAppClient:
|
2025-01-22 13:02:02 +00:00
|
|
|
def __init__(self, api_key: str):
|
2025-01-22 17:19:05 +00:00
|
|
|
assert api_key is not None, 'Missing API Key'
|
2025-01-22 13:02:02 +00:00
|
|
|
self.api_key = api_key
|
|
|
|
|
|
|
|
def _request_json(self, method: str, url: str, **kwargs) -> dict:
|
2025-01-22 17:19:05 +00:00
|
|
|
request_json_data = {'apiKey': self.api_key, 'language': 'en', **kwargs}
|
2025-01-22 16:31:00 +00:00
|
|
|
response = requests.request(
|
2025-01-22 17:19:05 +00:00
|
|
|
method=method, url=url, json=request_json_data,
|
2025-01-22 16:31:00 +00:00
|
|
|
)
|
2025-01-22 13:02:02 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
json_data = response.json()
|
|
|
|
if 'error' in json_data:
|
|
|
|
msg = 'Error from endpoint: {}'.format(json_data['error'])
|
2025-01-26 18:59:27 +00:00
|
|
|
raise ParcelsApiError(msg)
|
2025-01-22 13:02:02 +00:00
|
|
|
return json_data
|
|
|
|
|
|
|
|
def check_tracking_status(self, uuid: str) -> dict:
|
2025-01-22 17:19:05 +00:00
|
|
|
"""Function to check tracking status with UUID."""
|
2025-01-22 13:02:02 +00:00
|
|
|
json_data = self._request_json('GET', URL_TRACKING, uuid=uuid)
|
|
|
|
if json_data['done']:
|
|
|
|
logger.info('Tracking complete')
|
|
|
|
return json_data
|
|
|
|
else:
|
|
|
|
logger.info('Tracking in progress...')
|
|
|
|
time.sleep(TRACKING_STATUS_CHECKING_INTERVAL)
|
|
|
|
return self.check_tracking_status(uuid)
|
|
|
|
|
2025-01-22 14:58:55 +00:00
|
|
|
def _get_tracking_status_to_json(self, tracking_ids: list[str]) -> dict:
|
2025-01-22 16:31:00 +00:00
|
|
|
shipments = [
|
2025-01-22 17:19:05 +00:00
|
|
|
{'trackingId': tracking_id, 'country': target_country}
|
|
|
|
for tracking_id in tracking_ids
|
2025-01-22 16:31:00 +00:00
|
|
|
]
|
2025-01-22 13:02:02 +00:00
|
|
|
|
|
|
|
# Initiate tracking request
|
|
|
|
json_data = self._request_json('POST', URL_TRACKING, shipments=shipments)
|
|
|
|
if json_data.get('done'):
|
|
|
|
return json_data
|
2025-01-22 14:58:55 +00:00
|
|
|
|
2025-01-22 13:02:02 +00:00
|
|
|
return self.check_tracking_status(json_data['uuid'])
|
|
|
|
|
2025-01-22 14:58:55 +00:00
|
|
|
def get_tracking_status(self, tracking_ids: list[str]) -> Iterator[ParcelInfo]:
|
|
|
|
if len(tracking_ids) == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
for parcel_json in self._get_tracking_status_to_json(tracking_ids)['shipments']:
|
|
|
|
yield ParcelInfo(
|
2025-01-22 16:31:00 +00:00
|
|
|
tracking_number=parcel_json['trackingId'],
|
|
|
|
tracking_url=parcel_json['externalTracking'][0]['url'],
|
|
|
|
status=parcel_json['status'],
|
|
|
|
destination=parcel_json.get('destination'),
|
|
|
|
origin=parcel_json.get('origin'),
|
|
|
|
states=[
|
|
|
|
ParcelState(
|
|
|
|
status=s['status'],
|
|
|
|
date=datetime.datetime.fromisoformat(s['date']),
|
|
|
|
carrier=s['carrier'],
|
|
|
|
)
|
|
|
|
for s in parcel_json['states']
|
|
|
|
],
|
2025-01-22 14:58:55 +00:00
|
|
|
)
|