def _extract_pickup_details(response: Element, settings: Settings) -> PickupDetails: header = next((XP.build(PickupRequestHeaderType, elt) for elt in response.xpath(".//*[local-name() = $name]", name="pickup-request-header"))) price = next((XP.build(PickupRequestPriceType, elt) for elt in response.xpath(".//*[local-name() = $name]", name="pickup-request-price")), None) price_amount = sum( [ NF.decimal(price.hst_amount or 0.0), NF.decimal(price.gst_amount or 0.0), NF.decimal(price.due_amount or 0.0), ], 0.0, ) if price is not None else None return PickupDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, confirmation_number=header.request_id, pickup_date=DF.fdate(header.next_pickup_date), pickup_charge=ChargeDetails(name="Pickup fees", amount=NF.decimal(price_amount), currency="CAD") if price is not None else None, )
def _extract_shipment(response: Element, settings: Settings) -> ShipmentDetails: shipment = CreateShipmentResponse() document = DocumentDetail() shipment_nodes = response.xpath(".//*[local-name() = $name]", name="CreateShipmentResponse") document_nodes = response.xpath(".//*[local-name() = $name]", name="DocumentDetail") next((shipment.build(node) for node in shipment_nodes), None) next((document.build(node) for node in document_nodes), None) label = next( (content for content in [document.Data, document.URL] if content is not None), "No label returned", ) pin = cast(PIN, shipment.ShipmentPIN).Value return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=pin, shipment_identifier=pin, label=label, )
def _extract_details(postage_node: Element, settings: Settings) -> RateDetails: postage: PostageType = PostageType() postage.build(postage_node) currency = Currency.USD.name services: List[SpecialServiceType] = [ XP.build(SpecialServiceType, svc) for svc in postage_node.xpath(".//*[local-name() = $name]", name="SpecialService") ] estimated_date = DF.date(postage.CommitmentDate) transit = ((estimated_date - datetime.now()).days if estimated_date is not None else None) postage_rate = postage_node.find("Rate").text def get(key: str) -> Any: return reduce(lambda r, v: v.text, postage_node.findall(key), None) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=Service.find(get("MailService")).name, total_charge=NF.decimal(postage_rate), currency=currency, transit_days=transit, extra_charges=[ ChargeDetails( name=SpecialService(str(svc.ServiceID)).name, amount=NF.decimal(svc.Price), currency=currency, ) for svc in services ], )
def parse_error_response(response: Element, settings: Settings) -> List[Message]: notifications = response.xpath( ".//*[local-name() = $name]", name="Notifications") + response.xpath( ".//*[local-name() = $name]", name="Notification") errors = [_extract_error(node, settings) for node in notifications] + extract_fault(response, settings) return [error for error in errors if error is not None]
def extract(rates: List[RateDetails], detail_node: Element) -> List[RateDetails]: rate = XP.build(RatedShipmentType, detail_node) if rate.NegotiatedRateCharges is not None: total_charges = (rate.NegotiatedRateCharges.TotalChargesWithTaxes or rate.NegotiatedRateCharges.TotalCharge) taxes = rate.NegotiatedRateCharges.TaxCharges itemized_charges = rate.NegotiatedRateCharges.ItemizedCharges + taxes else: total_charges = rate.TotalChargesWithTaxes or rate.TotalCharges taxes = rate.TaxCharges itemized_charges = rate.ItemizedCharges + taxes extra_charges = itemized_charges + [rate.ServiceOptionsCharges] estimated_arrival = next( (XP.build(EstimatedArrivalType, n) for n in detail_node.xpath(".//*[local-name() = $name]", name="EstimatedArrival")), EstimatedArrivalType(), ) transit_days = (rate.GuaranteedDelivery.BusinessDaysInTransit if rate.GuaranteedDelivery is not None else estimated_arrival.BusinessDaysInTransit) currency_ = next( str(c.text) for c in detail_node.xpath(".//*[local-name() = $name]", name="CurrencyCode")) service = ShippingServiceCode(rate.Service.Code).name return rates + [ RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, currency=currency_, service=service, base_charge=NF.decimal( rate.TransportationCharges.MonetaryValue), total_charge=NF.decimal(total_charges.MonetaryValue), duties_and_taxes=reduce( lambda total, charge: total + NF.decimal(charge. MonetaryValue), taxes or [], 0.0, ), extra_charges=reduce( lambda total, charge: (total + [ ChargeDetails( name=charge.Code, amount=NF.decimal(charge.MonetaryValue), currency=charge.CurrencyCode, ) ]), [charge for charge in extra_charges if charge is not None], [], ), transit_days=NF.integer(transit_days), ) ]
def _extract_details(response: Element, settings: Settings) -> ShipmentDetails: shipment_node = next(iter(response.xpath(".//*[local-name() = $name]", name="shipment")), None) label = next(iter(response.xpath(".//*[local-name() = $name]", name="labels")), None) shipment = XP.to_object(Shipment, shipment_node) tracking_number = next(iter(shipment.packages), Package()).barcode return ShipmentDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, label=str(label.text), tracking_number=tracking_number, shipment_identifier=str(shipment.id), selected_rate=_extract_rate_details(shipment_node, settings), )
def _extract_shipment(response: Element, settings: Settings) -> ShipmentDetails: info_node = next( iter(response.xpath(".//*[local-name() = $name]", name="shipment-info")) ) label = next(iter(response.xpath(".//*[local-name() = $name]", name="label"))) errors = parse_error_response(label, settings) info: ShipmentInfoType = ShipmentInfoType() info.build(info_node) return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=info.tracking_pin, label=label.text if len(errors) == 0 else None, )
def _extract_tracking(shipment_node: Element, settings: Settings) -> TrackingDetails: track_detail = ShipmentType() track_detail.build(shipment_node) activity_nodes = shipment_node.xpath(".//*[local-name() = $name]", name="Activity") def build_activity(node) -> ActivityType: activity = ActivityType() activity.build(node) return activity activities: List[ActivityType] = list(map(build_activity, activity_nodes)) return TrackingDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=track_detail.InquiryNumber.Value, events=list( map( lambda a: TrackingEvent( date=format_date(a.Date, "%Y%m%d"), time=format_time(a.Time, "%H%M%S"), code=a.Status.Code if a.Status else None, location=(a.ActivityLocation.Address.City if a.ActivityLocation and a.ActivityLocation. Address else None), description=a.Status.Description if a.Status else None, ), activities, )), )
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: errors = parse_error_response(response, settings) reply = XP.to_object( ValidateCityPostalCodeZipResponse, next( iter( response.xpath(".//*[local-name() = $name]", name="ValidateCityPostalCodeZipResponse")), None)) address: ShortAddress = next( (result.Address for result in reply.SuggestedAddresses.SuggestedAddress), None) success = len(errors) == 0 validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success, complete_address=Address(city=address.City, state_code=address.Province, country_code=address.Country, postal_code=address.PostalCode) if address is not None else None) if success else None return validation_details, errors
def _extract_tracking(tracking_node: Element, settings) -> TrackingDetails: tracking: TrackInfoType = TrackInfoType() tracking.build(tracking_node) track_detail_nodes = tracking_node.xpath(".//*[local-name() = $name]", name="TrackDetail") details: List[TrackDetailType] = [ (lambda t: (t, t.build(detail)))(TrackDetailType())[0] for detail in track_detail_nodes ] return TrackingDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=tracking.TrackInfoID, events=[ TrackingEvent( code=str(event.EventCode), date=format_date(event.EventDate, "%B %d, %Y"), time=format_time(event.EventTime, "%H:%M %p"), description=event.ActionCode, location=", ".join([ location for location in [ event.EventCity, event.EventState, event.EventCountry, str(event.EventZIPCode), ] if location is not None ]), ) for event in details ], )
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: reply = XP.build( AddressValidationReply, next( iter( response.xpath(".//*[local-name() = $name]", name="AddressValidationReply")), None)) address: FedexAddress = next( (result.EffectiveAddress for result in reply.AddressResults), None) success = reply.HighestSeverity == NotificationSeverityType.SUCCESS.value _, lines = (address.StreetLines if address is not None and len(address.StreetLines) > 1 else ["", ""]) validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success, complete_address=Address(city=address.City, state_code=address.StateOrProvinceCode, country_code=address.CountryCode, residential=address.Residential, address_line1=next(iter(address.StreetLines), None), address_line2=SF.concat_str(lines, join=True)) if address is not None else None) if success else None return validation_details, parse_error_response(response, settings)
def _extract_details(shipment_node: Element, settings: Settings) -> TrackingDetails: track_detail = XP.build(ShipmentType, shipment_node) activities = [ XP.build(ActivityType, node) for node in shipment_node.xpath(".//*[local-name() = $name]", name="Activity") ] delivered = any(a.Status.Type == 'D' for a in activities) return TrackingDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=track_detail.InquiryNumber.Value, events=list( map( lambda a: TrackingEvent( date=DF.fdate(a.Date, "%Y%m%d"), description=a.Status.Description if a.Status else None, location=(_format_location(a.ActivityLocation.Address) if a.ActivityLocation is not None and a. ActivityLocation.Address is not None else None), time=DF.ftime(a.Time, "%H%M%S"), code=a.Status.Code if a.Status else None, ), activities, )), delivered=delivered)
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: errors = parse_error_response(response, settings) address_node = next( iter(response.xpath(".//*[local-name() = $name]", name="address")), None) address = XP.to_object(CanparAddress, address_node) success = len(errors) == 0 validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success, complete_address=Address(postal_code=address.postal_code, city=address.city, company_name=address.name, country_code=address.country, email=address.email, state_code=address.province, residential=address.residential, address_line1=address.address_line_1, address_line2=SF.concat_str( address.address_line_2, address.address_line_3, join=True))) if success else None return validation_details, errors
def _extract_intl_rates(service_node: Element, settings: Settings) -> RateDetails: service: ServiceType = ServiceType() service.build(service_node) currency = "USD" special_services: List[ExtraServiceType] = [ (lambda s: (s, s.build(svc)))(ExtraServiceType())[0] for svc in service_node.xpath(".//*[local-name() = $name]", name="ExtraService") ] delivery_date = (format_date(service.GuaranteeAvailability, "%m/%d/%Y") if service.GuaranteeAvailability is not None else None) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=service.SvcDescription, base_charge=decimal(service.Postage), total_charge=decimal(service.Postage), currency=currency, estimated_delivery=delivery_date, extra_charges=[ ChargeDetails( name=ExtraService(special.ServiceID).name, amount=decimal(special.Price), currency=currency, ) for special in special_services ], )
def _extract_details(service_node: Element, settings: Settings) -> RateDetails: service: ServiceType = ServiceType() service.build(service_node) currency = "USD" special_services: List[ExtraServiceType] = [ XP.build(ExtraServiceType, svc) for svc in service_node.xpath(".//*[local-name() = $name]", name="ExtraService") ] delivery_date = DF.date(service.GuaranteeAvailability, "%m/%d/%Y") transit = ((delivery_date - datetime.now()).days if delivery_date is not None else None) rate_service: Service = Service.find(service.SvcDescription) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=rate_service.value, base_charge=NF.decimal(service.Postage), total_charge=NF.decimal(service.Postage), currency=currency, transit_days=transit, extra_charges=[ ChargeDetails( name=ExtraService(special.ServiceID).name, amount=NF.decimal(special.Price), currency=currency, ) for special in special_services ], )
def parse_pickup_response( response: Element, settings: Settings) -> Tuple[PickupDetails, List[Message]]: pickup = (_extract_pickup_details(response, settings) if len( response.xpath(".//*[local-name() = $name]", name="pickup-request-header")) > 0 else None) return pickup, parse_error_response(response, settings)
def parse_non_contract_shipment_response( response: Element, settings: Settings) -> Tuple[ShipmentDetails, List[Message]]: shipment = (_extract_shipment(response, settings) if len( response.xpath(".//*[local-name() = $name]", name="shipment-id")) > 0 else None) return shipment, parse_error_response(response, settings)
def parse_rate_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: rate_reply = response.xpath(".//*[local-name() = $name]", name="RatedShipment") rates: List[RateDetails] = reduce(_extract_package_rate(settings), rate_reply, []) return rates, parse_error_response(response, settings)
def parse_shipment_response(response: Element, settings: Settings) -> Tuple[ShipmentDetails, List[Message]]: shipment = XP.to_object( Shipment, next(iter(response.xpath(".//*[local-name() = $name]", name="shipment")), None) ) success = (shipment is not None and shipment.id is not None) shipment_details = _extract_details(response, settings) if success else None return shipment_details, parse_error_response(response, settings)
def parse_full_estimate_response( response: Element, settings: Settings ) -> Tuple[List[RateDetails], List[Message]]: estimates = response.xpath(".//*[local-name() = $name]", name="ShipmentEstimate") return ( [_extract_rate(node, settings) for node in estimates], parse_error_response(response, settings), )
def parse_tracking_summary( response: Element, settings: Settings ) -> Tuple[List[TrackingDetails], List[Message]]: pin_summaries = response.xpath(".//*[local-name() = $name]", name="pin-summary") tracking: List[TrackingDetails] = [ _extract_tracking(pin, settings) for pin in pin_summaries ] return tracking, parse_error_response(response, settings)
def parse_price_quotes( response: Element, settings: Settings ) -> Tuple[List[RateDetails], List[Message]]: price_quotes = response.xpath(".//*[local-name() = $name]", name="price-quote") quotes: List[RateDetails] = [ _extract_quote(price_quote_node, settings) for price_quote_node in price_quotes ] return quotes, parse_error_response(response, settings)
def parse_tracking_response( response: Element, settings: Settings) -> Tuple[List[TrackingDetails], List[Message]]: track_details = response.xpath(".//*[local-name() = $name]", name="Shipment") tracking: List[TrackingDetails] = [ _extract_details(node, settings) for node in track_details ] return tracking, parse_error_response(response, settings)
def parse_dct_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: qtdshp_list = response.xpath(".//*[local-name() = $name]", name="QtdShp") quotes: List[RateDetails] = [ _extract_quote(qtdshp_node, settings) for qtdshp_node in qtdshp_list ] return ([quote for quote in quotes if quote is not None], parse_error_response(response, settings))
def parse_tracking_response( response: Element, settings: Settings) -> Tuple[List[TrackingDetails], List[Message]]: results = response.xpath(".//*[local-name() = $name]", name="result") details: List[TrackingDetails] = [ _extract_tracking_details(result, settings) for result in results ] return details, parse_error_response(response, settings)
def parse_track_package_response( response: Element, settings: Settings) -> Tuple[List[TrackingDetails], List[Message]]: track_infos = response.xpath(".//*[local-name() = $name]", name="TrackingInformation") return ( [_extract_tracking(node, settings) for node in track_infos], parse_error_response(response, settings), )
def parse_intl_rate_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: quotes: List[RateDetails] = [ _extract_intl_rates(package, settings) for package in response.xpath(".//*[local-name() = $name]", name="Service") ] return quotes, parse_error_response(response, settings)
def parse_rate_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: shipment_nodes = response.xpath(".//*[local-name() = $name]", name="shipment") rates: List[RateDetails] = [ _extract_rate_details(node, settings) for node in shipment_nodes ] return rates, parse_error_response(response, settings)
def parse_shipment_response( response: Element, settings: Settings) -> Tuple[ShipmentDetails, List[Message]]: details = next( iter( response.xpath(".//*[local-name() = $name]", name="ShipmentResults")), None) shipment = _extract_shipment(details, settings) if details is not None else None return shipment, parse_error_response(response, settings)
def parse_pickup_response(response: Element, settings: Settings) -> Tuple[PickupDetails, List[Message]]: pickup_node = next(iter(response.xpath(".//*[local-name() = $name]", name="pickup")), None) pickup = XP.build(PickupV2, pickup_node) details: PickupDetails = PickupDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, confirmation_number=str(pickup.id), pickup_date=DF.fdatetime(pickup.pickup_date, '%Y-%m-%dT%H:%M:%S') ) return details, parse_error_response(response, settings)