import logging import json import requests import xmltodict from lxml import etree from django.conf import settings from core import ShippingContainer logger = logging.getLogger(__name__) class USPSApiError(Exception): pass class USPSApi: BASE_URL = 'https://secure.shippingapis.com/ShippingAPI.dll?API=' urls = { 'validate': 'Verify&XML={xml}', 'rate': 'RateV4&XML={xml}' } def __init__(self, api_user_id, test=False): self.api_user_id = api_user_id self.test = test def get_url(self, action, xml): return self.BASE_URL + self.urls[action].format( **{'test': 'Certify' if self.test else '', 'xml': xml} ) def send_request(self, action, xml): # The USPS developer guide says "ISO-8859-1 encoding is the expected character set for the request." # (see https://www.usps.com/business/web-tools-apis/general-api-developer-guide.htm) xml = etree.tostring(xml, encoding='iso-8859-1', pretty_print=self.test).decode() url = self.get_url(action, xml) xml_response = requests.get(url).content response = json.loads(json.dumps(xmltodict.parse(xml_response))) if 'Error' in response: raise USPSApiError(response['Error']['Description']) return response def validate_address(self, *args, **kwargs): return AddressValidate(self, *args, **kwargs) def get_rate(self, *args, **kwargs): return Rate(self, *args, **kwargs) class AddressValidate: def __init__(self, usps, address): xml = etree.Element('AddressValidateRequest', {'USERID': usps.api_user_id}) _address = etree.SubElement(xml, 'Address', {'ID': '0'}) address.add_to_xml(_address, prefix='', validate=True) self.result = usps.send_request('validate', xml) class Rate: def __init__(self, usps, request, **kwargs): xml = etree.Element('RateV4Request', {'USERID': usps.api_user_id}) etree.SubElement(xml, 'Revision').text = '2' package = etree.SubElement(xml, 'Package', {'ID': '0'}) etree.SubElement(package, 'Service').text = request['service'] etree.SubElement(package, 'ZipOrigination').text = request['zip_origination'] etree.SubElement(package, 'ZipDestination').text = request['zip_destination'] etree.SubElement(package, 'Pounds').text = request['pounds'] etree.SubElement(package, 'Ounces').text = request['ounces'] etree.SubElement(package, 'Container').text = request['container'] etree.SubElement(package, 'Width').text = request['width'] etree.SubElement(package, 'Length').text = request['length'] etree.SubElement(package, 'Height').text = request['height'] etree.SubElement(package, 'Girth').text = request['girth'] etree.SubElement(package, 'Machinable').text = request['machinable'] self.result = usps.send_request('rate', xml)