Skip to content

Commit

Permalink
feat: add fedex raw mapper
Browse files Browse the repository at this point in the history
  • Loading branch information
danh91 committed Sep 29, 2024
1 parent cb9e301 commit b13aa0d
Show file tree
Hide file tree
Showing 31 changed files with 1,134 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def parse_pickup_response(
_response: lib.Deserializable[dict],
settings: provider_utils.Settings,
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
response = _response.deserialize()

messages = error.parse_error_response(response, settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def parse_pickup_update_response(
_response: lib.Deserializable[dict],
settings: provider_utils.Settings,
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
response = _response.deserialize()

messages = error.parse_error_response(response, settings)
Expand Down
4 changes: 4 additions & 0 deletions modules/connectors/fedex/generate
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ quicktype --src="${SCHEMAS}/tracking_request.json" --out="${LIB_MODULES}/trackin
quicktype --src="${SCHEMAS}/tracking_response.json" --out="${LIB_MODULES}/tracking_response.py"
quicktype --src="${SCHEMAS}/tracking_document_request.json" --out="${LIB_MODULES}/tracking_document_request.py"
quicktype --src="${SCHEMAS}/tracking_document_response.json" --out="${LIB_MODULES}/tracking_document_response.py"
quicktype --src="${SCHEMAS}/pickup_request.json" --out="${LIB_MODULES}/pickup_request.py"
quicktype --src="${SCHEMAS}/pickup_response.json" --out="${LIB_MODULES}/pickup_response.py"
quicktype --src="${SCHEMAS}/cancel_pickup_request.json" --out="${LIB_MODULES}/cancel_pickup_request.py"
quicktype --src="${SCHEMAS}/cancel_pickup_response.json" --out="${LIB_MODULES}/cancel_pickup_response.py"
28 changes: 28 additions & 0 deletions modules/connectors/fedex/karrio/mappers/fedex/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ def create_document_upload_request(
) -> lib.Serializable:
return provider.document_upload_request(payload, self.settings)

def create_pickup_request(self, payload: models.PickupRequest) -> lib.Serializable:
return provider.pickup_request(payload, self.settings)

def create_pickup_update_request(
self, payload: models.PickupUpdateRequest
) -> lib.Serializable:
return provider.pickup_update_request(payload, self.settings)

def create_cancel_pickup_request(
self, payload: models.PickupCancelRequest
) -> lib.Serializable:
return provider.pickup_cancel_request(payload, self.settings)

def parse_cancel_shipment_response(
self, response: lib.Deserializable
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
Expand All @@ -59,3 +72,18 @@ def parse_document_upload_response(
response: lib.Deserializable,
) -> typing.Tuple[models.DocumentUploadDetails, typing.List[models.Message]]:
return provider.parse_document_upload_response(response, self.settings)

def parse_cancel_pickup_response(
self, response: lib.Deserializable
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
return provider.parse_pickup_cancel_response(response, self.settings)

def parse_pickup_response(
self, response: lib.Deserializable
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
return provider.parse_pickup_response(response, self.settings)

def parse_pickup_update_response(
self, response: lib.Deserializable
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
return provider.parse_pickup_update_response(response, self.settings)
43 changes: 43 additions & 0 deletions modules/connectors/fedex/karrio/mappers/fedex/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,46 @@ def upload_document(self, requests: lib.Serializable) -> lib.Deserializable:
response,
lambda __: [lib.to_dict(_) for _ in __],
)

def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = lib.request(
url=f"{self.settings.server_url}/pickup/v1/pickups",
data=lib.to_json(request.serialize()),
trace=self.trace_as("json"),
method="POST",
headers={
"x-locale": "en_US",
"content-type": "application/json",
"authorization": f"Bearer {self.settings.access_token}",
},
decoder=provider_utils.parse_response,
on_error=lambda b: provider_utils.parse_response(b.read()),
)

return lib.Deserializable(response, lib.to_dict, request.ctx)

def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = self.cancel_pickup(lib.Serializable(request.ctx["cancel"]))
confirmation = response.deserialize().get("output") or {}

if confirmation.get("pickupConfirmationCode") is not None:
return self.schedule_pickup(request)

return response

def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
response = lib.request(
url=f"{self.settings.server_url}/pickup/v1/pickups/cancel",
data=lib.to_json(request.serialize()),
trace=self.trace_as("json"),
method="PUT",
headers={
"x-locale": "en_US",
"content-type": "application/json",
"authorization": f"Bearer {self.settings.access_token}",
},
decoder=provider_utils.parse_response,
on_error=lambda b: provider_utils.parse_response(b.read()),
)

return lib.Deserializable(response, lib.to_dict, request.ctx)
8 changes: 8 additions & 0 deletions modules/connectors/fedex/karrio/providers/fedex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@
parse_document_upload_response,
document_upload_request,
)
from karrio.providers.fedex.pickup import (
parse_pickup_cancel_response,
parse_pickup_update_response,
parse_pickup_response,
pickup_cancel_request,
pickup_update_request,
pickup_request,
)
8 changes: 6 additions & 2 deletions modules/connectors/fedex/karrio/providers/fedex/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ def parse_error_response(
*(result["errors"] if "errors" in result else []),
*(
result["output"]["alerts"]
if "alerts" in result.get("output", {})
if "output" in result
and not isinstance(result["output"], str)
and "alerts" in result.get("output", {})
and not isinstance(result["output"]["alerts"], str)
else []
),
*(
[{"message": result["output"]["message"]}]
if "message" in result.get("output", {})
if "output" in result
and not isinstance(result["output"], str)
and "message" in result.get("output", {})
and isinstance(result["output"]["message"], str)
and not result["output"]["alertType"] != "NOTE"
else []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from karrio.providers.fedex.pickup.create import parse_pickup_response, pickup_request
from karrio.providers.fedex.pickup.update import (
parse_pickup_update_response,
pickup_update_request,
)
from karrio.providers.fedex.pickup.cancel import (
parse_pickup_cancel_response,
pickup_cancel_request,
)
79 changes: 79 additions & 0 deletions modules/connectors/fedex/karrio/providers/fedex/pickup/cancel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import karrio.schemas.fedex.cancel_pickup_request as fedex
import karrio.schemas.fedex.cancel_pickup_response as pickup

import typing
import karrio.lib as lib
import karrio.core.units as units
import karrio.core.models as models
import karrio.providers.fedex.error as error
import karrio.providers.fedex.utils as provider_utils
import karrio.providers.fedex.units as provider_units


def parse_pickup_cancel_response(
_response: lib.Deserializable[dict],
settings: provider_utils.Settings,
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
response = _response.deserialize()
messages = error.parse_error_response(response, settings)
success = any(lib.failsafe(lambda: response["output"]["pickupConfirmationCode"]))

confirmation = lib.identity(
models.ConfirmationDetails(
carrier_id=settings.carrier_id,
carrier_name=settings.carrier_name,
operation="Cancel Pickup",
success=success,
)
if success
else None
)

return confirmation, messages


def pickup_cancel_request(
payload: models.PickupCancelRequest,
settings: provider_utils.Settings,
) -> lib.Serializable:
address = lib.to_address(payload.address)
options = lib.units.Options(
payload.options,
option_type=lib.units.create_enum(
"PickupOptions",
# fmt: off
{
"fedex_carrier_code": lib.OptionEnum("carrierCode"),
"fedex_pickup_location": lib.OptionEnum("location"),
},
# fmt: on
),
)

# map data to convert karrio model to fedex specific type
request = fedex.CancelPickupRequestType(
associatedAccountNumber=fedex.AssociatedAccountNumberType(
value=settings.account_number,
),
pickupConfirmationCode=payload.confirmation_number,
remarks=payload.reason,
carrierCode=options.fedex_carrier_code.state,
accountAddressOfRecord=lib.identity(
fedex.AccountAddressOfRecordType(
streetLines=address.address_lines,
urbanizationCode=None,
city=address.city,
stateOrProvinceCode=provider_utils.state_code(address),
postalCode=address.postal_code,
countryCode=address.country_code,
residential=address.residential,
addressClassification=None,
)
if payload.address
else None
),
scheduledDate=lib.fdate(payload.pickup_date, "%Y-%m-%d"),
location=options.fedex_pickup_location.state,
)

return lib.Serializable(request, lib.to_dict)
148 changes: 148 additions & 0 deletions modules/connectors/fedex/karrio/providers/fedex/pickup/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import karrio.schemas.fedex.pickup_request as fedex
import karrio.schemas.fedex.pickup_response as pickup

import typing
import karrio.lib as lib
import karrio.core.units as units
import karrio.core.models as models
import karrio.providers.fedex.error as error
import karrio.providers.fedex.utils as provider_utils
import karrio.providers.fedex.units as provider_units


def parse_pickup_response(
_response: lib.Deserializable[dict],
settings: provider_utils.Settings,
) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
response = _response.deserialize()

messages = error.parse_error_response(response, settings)
pickup = lib.identity(
_extract_details(response, settings, _response.ctx)
if (response.get("output") or {}).get("pickupConfirmationCode")
else None
)

return pickup, messages


def _extract_details(
data: dict,
settings: provider_utils.Settings,
ctx: dict,
) -> models.PickupDetails:
details = lib.to_object(pickup.PickupResponseType, data)

return models.PickupDetails(
carrier_id=settings.carrier_id,
carrier_name=settings.carrier_name,
confirmation_number=details.output.pickupConfirmationCode,
pickup_date=lib.fdate(ctx["pickup_date"]),
)


def pickup_request(
payload: models.PickupRequest,
settings: provider_utils.Settings,
) -> lib.Serializable:
address = lib.to_address(payload.address)
packages = lib.to_packages(payload.parcels)
options = lib.units.Options(
payload.options,
option_type=lib.units.create_enum(
"PickupOptions",
# fmt: off
{
"fedex_carrier_code": lib.OptionEnum("carrierCode"),
"fedex_pickup_location": lib.OptionEnum("location"),
"fedex_user_message": lib.OptionEnum("userMessage"),
"fedex_early_pickup": lib.OptionEnum("earlyPickup"),
"fedex_building_part": lib.OptionEnum("buildingPart"),
"fedex_pickup_date_type": lib.OptionEnum("pickupDateType"),
"fedex_supplies_requested": lib.OptionEnum("suppliesRequested"),
"fedex_pickup_address_type": lib.OptionEnum("pickupAddressType"),
"fedex_building_part_description": lib.OptionEnum("buildingPartDescription"),
"fedex_associated_account_number_type": lib.OptionEnum("associatedAccountNumberType"),
},
# fmt: on
),
)

# map data to convert karrio model to fedex specific type
request = fedex.PickupRequestType(
associatedAccountNumber=fedex.AccountNumberType(
value=settings.account_number,
),
originDetail=fedex.OriginDetailType(
pickupAddressType=options.fedex_pickup_address_type.state,
pickupLocation=fedex.PickupLocationType(
contact=fedex.ContactType(
companyName=address.company_name,
personName=address.person_name,
phoneNumber=address.phone_number,
phoneExtension=None,
),
address=fedex.AccountAddressOfRecordType(
streetLines=address.address_lines,
city=address.city,
stateOrProvinceCode=address.state_code,
postalCode=address.postal_code,
countryCode=address.country_code,
residential=address.residential,
addressClassification=None,
urbanizationCode=None,
),
accountNumber=fedex.AccountNumberType(
value=settings.account_number,
),
deliveryInstructions=options.instructions.state,
),
readyDateTimestamp=f"{payload.pickup_date}T{payload.closing_time}:00Z",
customerCloseTime=f"{payload.closing_time}:00",
pickupDateType=options.fedex_pickup_date_type.state,
packageLocation=payload.package_location,
buildingPart=options.fedex_building_part.state,
buildingPartDescription=options.fedex_building_part_description.state,
earlyPickup=options.fedex_early_pickup.state,
suppliesRequested=options.fedex_supplies_requested.state,
geographicalPostalCode=options.geographical_postal_code.state,
),
associatedAccountNumberType=options.fedex_associated_account_number_type.state,
totalWeight=fedex.TotalWeightType(
units=packages.weight_unit,
value=packages.weight.value,
),
packageCount=len(packages),
carrierCode=options.fedex_carrier_code.state or "FDXE",
accountAddressOfRecord=None,
remarks=None,
countryRelationships=None,
pickupType=None,
trackingNumber=None,
commodityDescription=lib.text(packages.description, max=100),
expressFreightDetail=None,
oversizePackageCount=None,
pickupNotificationDetail=lib.identity(
fedex.PickupNotificationDetailType(
emailDetails=[
fedex.EmailDetailType(
address=address.email,
locale="en_US",
)
],
format="TEXT",
userMessage=options.fedex_user_message.state,
)
if address.email
else None
),
)

return lib.Serializable(
request,
lib.to_dict,
dict(
pickup_date=payload.pickup_date,
address=payload.address,
),
)
Loading

0 comments on commit b13aa0d

Please sign in to comment.