from datetime import datetime from urbantz.base import JSONSerializable, Coordinates from urbantz.exceptions import APIError import requests class Delivery(JSONSerializable): """ A UrbanTZ delivery with a unique ID. """ def __init__(self, tracking_code=None): """ :param tracking_code: A delivery public tracking code. :type tracking_code: str or None """ self.tracking_code = tracking_code """ The delivery public tracking code. :type: str or None """ self.last_updated = None """ Last API update date/time. Is None if data has never been fetched from the API. :type: datetime or None """ self.payload = None """ Latest parsed JSON payload from the API. Is None if data has never been fetched from the API or loaded via :meth:`Delivery.use`. :type: dict or None """ self.position = None """ Coordinates of the delivery truck's position. :type: urbantz.base.Coordinates """ self.destination = None """ Coordinates of the delivery destination. :type: urbantz.base.Coordinates """ def __repr__(self): return '{}({})'.format( self.__class__.__name__, repr(self.tracking_code)) @property def api_url(self): """ URL pointing to the API endpoint to use for the specific delivery. :type: str """ return 'https://backend.urbantz.com/public/task/tracking/{}'.format( self.tracking_code) def update(self): """ Fetch the latest delivery information from the API. :raises urbantz.exceptions.APIError: If the API returned a JSON error. :raises requests.exceptions.HTTPError: If the response has an HTTP 4xx or 5xx code, or an empty payload. """ resp = requests.get(self.api_url) data = {} try: data = resp.json() except Exception: pass if 'error' in data: raise APIError(data['error']) resp.raise_for_status() if not data: # If requests does not raise anything and there is no data, # raise our own error raise APIError({'message': 'API returned an empty payload'}) self.use(data) # TODO: See if the payload holds a last update value self.last_updated = datetime.now() def use(self, payload): """ Use a parsed JSON payload to update the properties. :param dict payload: A parsed JSON payload from the API. """ self.payload = payload self.position = Coordinates.fromJSON(self.payload['position']) self.destination = Coordinates.fromJSON( self.payload['location']['location']['geometry']) @classmethod def fromJSON(cls, payload): """ Create a Delivery instance from an existing payload. :param payload: A parsed JSON payload. :type payload: dict """ instance = cls() instance.use(payload) return instance def toJSON(self): return self.payload