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 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_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 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_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_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 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(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 _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 _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 _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_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_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_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_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]]: 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)