def update(payload: dict, carrier: models.Carrier = None, **carrier_filters) -> datatypes.PickupResponse: carrier = carrier or Carriers.first( **{ **dict(active=True, capability="pickup", raise_not_found=True), **carrier_filters, }) if carrier is None: raise NotFound( "No active carrier connection found to process the request") request = purplship.Pickup.update( datatypes.PickupUpdateRequest(**DP.to_dict(payload))) # The request call is wrapped in identity to simplify mocking in tests pickup, messages = identity( lambda: request.from_(carrier.gateway).parse()) if pickup is None: raise exceptions.PurplShipApiException( detail=datatypes.ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST, ) return datatypes.PickupResponse( pickup=datatypes.Pickup( **{ **payload, **DP.to_dict(pickup), "test_mode": carrier.test, }), messages=messages, )
def validate(payload: dict) -> datatypes.AddressValidation: validation = validators.Address.validate(datatypes.Address(**payload)) if validation.success is False: raise exceptions.PurplShipApiException(detail=validation, code="invalid_address") return validation
def fetch(payload: dict, carriers: List[models.Carrier] = None, **carrier_filters) -> datatypes.RateResponse: carrier_ids = payload.get("carrier_ids", []) carriers = carriers or Carriers.list( **{ **dict(active=True, capability="rating", carrier_ids=carrier_ids), **carrier_filters, }) gateways = [ c.gateway for c in carriers if "get_rates" in c.gateway.capabilities ] if len(gateways) == 0: raise NotFound( "No active carrier connection found to process the request") request = purplship.Rating.fetch( datatypes.RateRequest(**DP.to_dict(payload))) # The request call is wrapped in identity to simplify mocking in tests rates, messages = identity(lambda: request.from_(*gateways).parse()) if not any(rates) and any(messages): raise exceptions.PurplShipApiException( detail=datatypes.ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST, ) def process_rate(rate: datatypes.Rate) -> datatypes.Rate: carrier = next( (c for c in carriers if c.carrier_id == rate.carrier_id)) meta = { **(rate.meta or {}), "rate_provider": ((rate.meta or {}).get("rate_provider") or rate.carrier_name).lower(), "service_name": upper((rate.meta or {}).get("service_name") or rate.service), } return datatypes.Rate( **{ **DP.to_dict(rate), "id": f"rat_{uuid.uuid4().hex}", "carrier_ref": carrier.id, "test_mode": carrier.test, "meta": meta, }) formated_rates: List[datatypes.Rate] = sorted( map(process_rate, rates), key=lambda rate: rate.total_charge) return datatypes.RateResponse(rates=formated_rates, messages=messages)
def cancel(payload: dict, carrier: models.Carrier = None, **carrier_filters) -> datatypes.ConfirmationResponse: carrier = carrier or Carriers.first( **{ **dict(active=True, capability="shipping", raise_not_found=True), **carrier_filters, }) if carrier is None: raise NotFound( "No active carrier connection found to process the request") request = purplship.Shipment.cancel( datatypes.ShipmentCancelRequest(**payload)) # The request call is wrapped in identity to simplify mocking in tests confirmation, messages = ( identity(lambda: request.from_(carrier.gateway).parse()) if "cancel_shipment" in carrier.gateway.capabilities else ( datatypes.Confirmation( carrier_name=carrier.gateway.settings.carrier_name, carrier_id=carrier.gateway.settings.carrier_id, success=True, operation="Safe cancellation allowed", ), [], )) if confirmation is None: raise exceptions.PurplShipApiException( detail=datatypes.ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST, ) return datatypes.ConfirmationResponse(confirmation=confirmation, messages=messages)
def track(payload: dict, carrier: models.Carrier = None, **carrier_filters) -> datatypes.TrackingResponse: carrier = carrier or Carriers.first( **{ **dict(active=True, capability="tracking", raise_not_found=True), **carrier_filters, }) if carrier is None: raise NotFound( "No active carrier connection found to process the request") request = purplship.Tracking.fetch( datatypes.TrackingRequest(**DP.to_dict(payload))) # The request call is wrapped in identity to simplify mocking in tests results, messages = identity( lambda: request.from_(carrier.gateway).parse()) if not any(results or []): raise exceptions.PurplShipApiException( detail=datatypes.ErrorResponse(messages=messages), status_code=status.HTTP_404_NOT_FOUND, ) return datatypes.TrackingResponse( tracking=(datatypes.Tracking( **{ **DP.to_dict(results[0]), "id": f"trk_{uuid.uuid4().hex}", "test_mode": carrier.test, "status": compute_tracking_status(results[0]).value, }) if any(results) else None), messages=messages, )
def create( payload: dict, carrier: models.Carrier = None, resolve_tracking_url: Callable[[str, str], str] = None, ) -> datatypes.Shipment: selected_rate = next( (datatypes.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 models.Carrier.objects.get( carrier_id=selected_rate.carrier_id) request = datatypes.ShipmentRequest( **{ **DP.to_dict(payload), "service": selected_rate.service }) # The request is wrapped in identity to simplify mocking in tests shipment, messages = identity(lambda: purplship.Shipment.create( request).from_(carrier.gateway).parse()) if shipment is None: raise exceptions.PurplShipApiException( detail=datatypes.ErrorResponse(messages=messages), status_code=status.HTTP_400_BAD_REQUEST, ) def process_meta(parent) -> dict: return { **(parent.meta or {}), "rate_provider": ((parent.meta or {}).get("rate_provider") or carrier.carrier_name).lower(), "service_name": upper((parent.meta or {}).get("service_name") or selected_rate.service), } def process_selected_rate() -> dict: rate = ({ **DP.to_dict(shipment.selected_rate), "id": f"rat_{uuid.uuid4().hex}", "test_mode": carrier.test, } if shipment.selected_rate is not None else DP.to_dict(selected_rate)) return { **rate, "meta": process_meta(shipment.selected_rate or selected_rate), } def process_tracking_url(rate: datatypes.Rate) -> str: rate_provider = (rate.get("meta") or {}).get("rate_provider") if (rate_provider not in models.MODELS) and ( (shipment.meta or {}).get("tracking_url") is not None): return shipment.meta["tracking_url"] if resolve_tracking_url is not None: url = resolve_tracking_url(shipment.tracking_number, rate_provider or rate.carrier_name) return f"{url}{'?test' if carrier.test else ''}" return "" shipment_rate = process_selected_rate() return datatypes.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": process_tracking_url(shipment_rate), "status": serializers.ShipmentStatus.purchased.value, "created_at": datetime.now().strftime( "%Y-%m-%d %H:%M:%S.%f%z"), "messages": messages, "meta": process_meta(shipment), })