def cancel(payload: dict, carrier_filter: dict = None, carrier: models.Carrier = None) -> ConfirmationResponse: carrier = carrier or next( iter(Carriers.list(**{ **(carrier_filter or {}), 'active': True })), None) print(carrier) if carrier is None: raise NotFound('No configured and active carrier found') request = purplship.Pickup.cancel( PickupCancelRequest(**DP.to_dict(payload))) gateway = purplship.gateway[carrier.data.carrier_name].create( carrier.data.dict()) # The request call is wrapped in identity to simplify mocking in tests confirmation, messages = identity( lambda: request.from_(gateway).parse()) if confirmation is None: raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST) return ConfirmationResponse(confirmation=confirmation, messages=messages)
def track(payload: dict, carrier_filter: dict = None, carrier: models.Carrier = None) -> TrackingResponse: carrier = carrier or next( iter(Carriers.list(**{ **(carrier_filter or {}), 'active': True })), None) if carrier is None: raise NotFound('No configured and active carrier found') request = purplship.Tracking.fetch( TrackingRequest(**DP.to_dict(payload))) gateway = purplship.gateway[carrier.data.carrier_name].create( carrier.data.dict()) # The request call is wrapped in identity to simplify mocking in tests results, messages = identity(lambda: request.from_(gateway).parse()) if any(messages or []) and not any(results or []): raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_404_NOT_FOUND) return TrackingResponse(tracking=(Tracking( **{ **DP.to_dict(results[0]), 'id': f'trk_{uuid.uuid4().hex}', 'test_mode': carrier.test, }) if any(results) else None), messages=messages)
def schedule(payload: dict, carrier_filter: dict = None, carrier: models.Carrier = None) -> PickupResponse: carrier = carrier or next( iter(Carriers.list(**{ **(carrier_filter or {}), 'active': True })), None) if carrier is None: raise NotFound('No configured and active carrier found') request = purplship.Pickup.schedule( PickupRequest(**DP.to_dict(payload))) gateway = purplship.gateway[carrier.data.carrier_name].create( carrier.data.dict()) # The request call is wrapped in identity to simplify mocking in tests pickup, messages = identity(lambda: request.from_(gateway).parse()) if pickup is None: raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST) return PickupResponse(pickup=Pickup( **{ **payload, **DP.to_dict(pickup), 'id': f'pck_{uuid.uuid4().hex}', 'test_mode': carrier.test, }), messages=messages)
def post(self, request: Request, pk: str): """ Select your preferred rates to buy a shipment label. """ shipment = request.user.shipment_set.get(pk=pk) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( f"The shipment is '{shipment.status}' and therefore already {ShipmentStatus.purchased.value}", code='state_error', status_code=status.HTTP_409_CONFLICT) payload = { **Shipment(shipment).data, **SerializerDecorator[ShipmentPurchaseData](data=request.data).data } # Submit shipment to carriers response: Shipment = SerializerDecorator[ShipmentValidationData]( data=payload).save(user=request.user).instance # Update shipment state SerializerDecorator[ShipmentSerializer]( shipment, data=DP.to_dict(response)).save() return Response(Shipment(shipment).data)
def post(self, request: Request, pk: str): """ Add one or many options to your shipment.<br/> **eg:**<br/> - add shipment **insurance** - specify the preferred transaction **currency** - setup a **cash collected on delivery** option ```json { "insurance": 120, "currency": "USD" } ``` And many more, check additional options available in the [reference](#operation/all_references). """ shipment = request.user.shipment_set.get(pk=pk) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException("Shipment already 'purchased'", code='state_error', status_code=status.HTTP_409_CONFLICT) payload: dict = DP.to_dict(dict(options=request.data)) SerializerDecorator[ShipmentSerializer](shipment, data=payload).save() reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
def create(payload: dict, resolve_tracking_url: Callable[[Shipment], str] = None, carrier: models.Carrier = None) -> Shipment: selected_rate = next( (Rate(**rate) for rate in payload.get('rates') if rate.get('id') == payload.get('selected_rate_id')), None) if selected_rate is None: raise NotFound( f'Invalid selected_rate_id "{payload.get("selected_rate_id")}" \n ' f'Please select one of the following: [ {", ".join([r.get("id") for r in payload.get("rates")])} ]' ) carrier = carrier or Carriers.retrieve( carrier_id=selected_rate.carrier_id).data request = ShipmentRequest( **{ **DP.to_dict(payload), 'service': selected_rate.service }) gateway = purplship.gateway[carrier.carrier_name].create( carrier.dict()) # The request is wrapped in identity to simplify mocking in tests shipment, messages = identity( lambda: purplship.Shipment.create(request).from_(gateway).parse()) if shipment is None: raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST) shipment_rate = ({ **DP.to_dict(shipment.selected_rate), 'id': f'rat_{uuid.uuid4().hex}' } if shipment.selected_rate is not None else DP.to_dict(selected_rate)) def generate_tracking_url(): if resolve_tracking_url is None: return '' return f"{resolve_tracking_url(shipment)}{'?test' if carrier.test else ''}" return Shipment( **{ **payload, **DP.to_dict(shipment), "id": f"shp_{uuid.uuid4().hex}", "test_mode": carrier.test, "selected_rate": shipment_rate, "service": shipment_rate["service"], "selected_rate_id": shipment_rate["id"], "tracking_url": generate_tracking_url(), "status": ShipmentStatus.purchased.value, "created_at": datetime.now().strftime( "%Y-%m-%d %H:%M:%S.%f%z"), "messages": messages })
def post(self, request: Request, pk: str): """ Add the customs declaration for the shipment if non existent. """ shipment = request.user.shipment_set.get(pk=pk) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException("Shipment already 'purchased'", code='state_error', status_code=status.HTTP_409_CONFLICT) if shipment.customs is not None: raise PurplShipApiException( "Shipment customs declaration already defined", code='state_error', status_code=status.HTTP_409_CONFLICT) SerializerDecorator[ShipmentSerializer]( shipment, data=dict(customs=request.data)).save() reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
def patch(self, request: Request, pk: str): """ modify an existing parcel's details. """ parcel = request.user.parcel_set.get(pk=pk) shipment = parcel.shipment_parcels.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this parcel has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error') SerializerDecorator[ParcelSerializer](parcel, data=request.data).save() reset_related_shipment_rates(shipment) return Response(Parcel(parcel).data)
def delete(self, request: Request, pk: str): """ Void a shipment with the associated label. """ shipment = request.user.shipment_set.get(pk=pk) if shipment.status not in [ ShipmentStatus.purchased.value, ShipmentStatus.created.value ]: raise PurplShipApiException( f"The shipment is '{shipment.status}' and can therefore not be cancelled anymore...", code='state_error', status_code=status.HTTP_409_CONFLICT) if shipment.pickup_shipments.exists(): raise PurplShipApiException(( f"This shipment is scheduled for pickup '{shipment.pickup_shipments.first().pk}' " "Please cancel this shipment from the pickup before."), code='state_error', status_code=status.HTTP_409_CONFLICT) confirmation = SerializerDecorator[ShipmentCancelSerializer]( shipment, data={}).save() return Response(OperationResponse(confirmation.instance).data)
def patch(self, request: Request, pk: str): """ update an address. """ address = request.user.address_set.get(pk=pk) shipment = address.shipper.first() or address.recipient.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this address has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error' ) SerializerDecorator[AddressSerializer](address, data=request.data).save() reset_related_shipment_rates(shipment) return Response(Address(address).data)
def patch(self, request: Request, pk: str): """ modify an existing customs declaration. """ customs = request.user.customs_set.get(pk=pk) shipment = customs.shipment_set.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this customs info has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error') SerializerDecorator[CustomsSerializer]( customs, data=request.data).save(user=request.user) reset_related_shipment_rates(shipment) return Response(Customs(customs).data)
def post(self, request: Request, pk: str): """ Add a parcel to an existing shipment for a multi-parcel shipment. """ shipment = request.user.shipment_set.get(pk=pk) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException("Shipment already 'purchased'", code='state_error', status_code=status.HTTP_409_CONFLICT) parcel = SerializerDecorator[ParcelSerializer](data=request.data).save( user=request.user).instance shipment.shipment_parcels.add(parcel) reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
def fetch(payload: dict, user=None) -> RateResponse: request = purplship.Rating.fetch(RateRequest(**DP.to_dict(payload))) carrier_settings_list = [ carrier.data for carrier in Carriers.list(carrier_ids=payload.get( 'carrier_ids', []), active=True, user=user) ] gateways = [ purplship.gateway[c.carrier_name].create(c.dict()) for c in carrier_settings_list ] compatible_gateways = [ g for g in gateways if 'get_rates' in g.features ] if len(compatible_gateways) == 0: raise NotFound("No configured and active carriers specified") # The request call is wrapped in identity to simplify mocking in tests rates, messages = identity( lambda: request.from_(*compatible_gateways).parse()) if not any(rates) and any(messages): raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST) def consolidate_rate(rate: Rate) -> Rate: carrier = next((c for c in carrier_settings_list if c.carrier_id == rate.carrier_id)) return Rate( **{ 'id': f'rat_{uuid.uuid4().hex}', 'carrier_ref': carrier.id, 'test_mode': carrier.test, **DP.to_dict(rate) }) rates: List[Rate] = sorted(map(consolidate_rate, rates), key=lambda rate: rate.total_charge) return RateResponse(rates=sorted(rates, key=lambda rate: rate.total_charge), messages=messages)
def delete(self, request: Request, pk: str): """ Discard a customs declaration. """ customs = request.user.customs_set.get(pk=pk) shipment = customs.shipment_set.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this customs info has been 'purchased' and cannot be discarded", status_code=status.HTTP_409_CONFLICT, code='state_error') customs.delete(keep_parents=True) shipment.customs = None serializer = Operation( dict(operation="Discard customs info", success=True)) reset_related_shipment_rates(shipment) return Response(serializer.data)
def validate(payload: dict, carrier_filter: dict) -> AddressValidation: carrier = next(iter(Carriers.list(**(carrier_filter or {}))), None) if carrier is None: raise NotFound('No configured and active carrier found') request = purplship.Address.validate( AddressValidationRequest(**DP.to_dict(payload))) gateway = purplship.gateway[carrier.data.carrier_name].create( carrier.data.dict()) # The request call is wrapped in identity to simplify mocking in tests validation, messages = identity(lambda: request.from_(gateway).parse()) if validation is None: raise PurplShipApiException( detail=ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST) return AddressValidation(validation=validation, messages=messages)
def delete(self, request: Request, pk: str): """ Remove a parcel. """ parcel = request.user.parcel_set.get(pk=pk) shipment = parcel.shipment_parcels.first() if shipment is not None and ( shipment.status == ShipmentStatus.purchased.value or len(shipment.shipment_parcels.all()) == 1): raise PurplShipApiException( "A shipment attached to this parcel is purchased or has only one parcel. The parcel cannot be removed!", status_code=status.HTTP_409_CONFLICT, code='state_error') parcel.delete(keep_parents=True) shipment.shipment_parcels.set( shipment.shipment_parcels.exclude(id=parcel.id)) serializer = Operation(dict(operation="Remove parcel", success=True)) reset_related_shipment_rates(shipment) return Response(serializer.data)