Beispiel #1
0
    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,
        )
Beispiel #2
0
    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
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
    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,
        )
Beispiel #6
0
    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),
            })