Skip to content

Instantly share code, notes, and snippets.

@mtik00
Created June 24, 2017 21:39
Show Gist options
  • Save mtik00/1b0f2e018f4d583b24df9cb0652cbf7e to your computer and use it in GitHub Desktop.
Save mtik00/1b0f2e018f4d583b24df9cb0652cbf7e to your computer and use it in GitHub Desktop.
USPS Address Standardization
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
This script shows an example of using `requests` and the USPS Address
Information API.
In order to use this, you must first register so you can get your USERID. Your
ID must be in the environment variable `USPS_USERID`.
For information on the API see here:
https://www.usps.com/business/web-tools-apis/address-information-api.htm
'''
# Imports #####################################################################
import os
import xml.dom.minidom
import xml.etree.ElementTree as ET
import requests
# Globals #####################################################################
USPS_USERID = os.environ['USPS_USERID']
def dump_xml(raw_xml):
'''
Return a string representation of XML with proper intendation
'''
if isinstance(raw_xml, ET.Element):
raw_xml = ET.tostring(raw_xml)
return xml.dom.minidom.parseString(raw_xml).toprettyxml()
def get_text(root, xpath):
'''
Return the text of the XPath element, or None if the element was not found.
'''
element = root.find(xpath)
if element is not None:
return element.text
return ''
class USPSAddress(object):
'''Representation of an United States Postal Service address.'''
def __init__(
self, name='', suite='', street='', city='', state='',
zip5='', zip4=''
):
self.name = name
self.suite = suite # or APT
self.street = street.title()
self.city = city.title()
self.state = state.upper() if len(state) == 2 else state.title()
self.zip5 = zip5
self.zip4 = zip4
self._standardized = ''
def zipcode(self):
'''Returns the zipcode based on whether or not `zip4` is used.'''
if self.zip4:
return "%s-%s" % (self.zip5, self.zip4)
return self.zip5
def original(self):
'''Return the non-standardized address format'''
lines = [
self.name,
self.suite,
self.street,
"{0}, {1} {2}".format(self.city, self.state, self.zipcode())
]
return '\n'.join([x for x in lines if x])
def standardized(self):
'''Return the standardized address format'''
if self._standardized:
return self._standardized
api = AddressStandardizationWebTool(
self.street, self.city, self.state, self.name, self.suite,
self.zip5, self.zip4)
result = api.get_standardized_address()
lines = [result['name']]
if result['suite']:
lines.append('%s %s' % (result['street'], result['suite']))
else:
lines.append(result['street'])
if result['zip4']:
lines.append(
'%s %s %s-%s' % (
result['city'], result['state'], result['zip5'],
result['zip4'])
)
else:
lines.append(
'%s %s %s' % (result['city'], result['state'], result['zip5'])
)
self._standardized = '\n'.join([x for x in lines if x])
return self._standardized
class USPSShippingAPI(object):
'''
Representation of the USPS Shipping API
https://www.usps.com/business/web-tools-apis/address-information-api.htm
'''
url = "http://production.shippingapis.com/ShippingAPI.dll"
def __init__(self, api, userid=USPS_USERID):
self.userid = userid
self.api = api
def _xml_payload(self):
'''
Child classes should override this to return a string appropriate for
its API.
'''
raise Exception("Must override this function!")
def send_request(self):
'''
Send the request and return the XML response.
'''
response = requests.get(
USPSShippingAPI.url,
params={'API': self.api, 'XML': self._xml_payload()}
)
root = ET.fromstring(response.content)
if bool(
(response.status_code != 200) or
(root.tag.lower() == 'error') or
root.findall('.//Error')
):
print dump_xml(response.content)
raise Exception('Invalid response')
# Check for any returned messages. There's shouldn't be any, so treat
# it as a warning.
text = root.find('.//ReturnText')
if text is not None:
print "WARNING: ", text.text
return root
class AddressStandardizationWebTool(USPSShippingAPI):
'''
Object to get a standardized USPS Address.
'''
api = "Verify"
def __init__(
self, street, city, state, name=None, suite=None,
zip5=None, zip4=None, userid=USPS_USERID
):
super(AddressStandardizationWebTool, self).__init__(
userid=userid, api=AddressStandardizationWebTool.api)
self.name = name
self.suite = suite # or APT
self.street = street
self.city = city
self.state = state
self.zip5 = zip5
self.zip4 = zip4
def get_standardized_address(self):
'''
Returns a standardized format of the object's address.
'''
root = self.send_request()
return {
'name': get_text(root, "./Address/FirmName"),
'suite': get_text(root, "./Address/Address1"),
'street': get_text(root, "./Address/Address2"),
'city': get_text(root, "./Address/City"),
'state': get_text(root, "./Address/State"),
'zip5': get_text(root, "./Address/Zip5"),
'zip4': get_text(root, "./Address/Zip4")
}
def _xml_payload(self):
'''
Returns the appropriate XML format for an 'Address Standardization Web
Tool' request.
'''
root = ET.Element('AddressValidateRequest')
root.set('USERID', self.userid)
include_opt = ET.Element('IncludeOptionalElements')
include_opt.text = 'false'
root.append(include_opt)
return_carrier_route = ET.Element('ReturnCarrierRoute')
return_carrier_route.text = 'false'
root.append(return_carrier_route)
address = ET.Element('Address')
address.set('ID', '0')
firm_element = ET.Element('FirmName')
firm_element.text = self.name
address.append(firm_element)
address1_element = ET.Element('Address1')
address1_element.text = self.suite
address.append(address1_element)
address2_element = ET.Element('Address2')
address2_element.text = self.street
address.append(address2_element)
city_element = ET.Element('City')
city_element.text = self.city
address.append(city_element)
state_element = ET.Element('State')
state_element.text = self.state
address.append(state_element)
zip5_element = ET.Element('Zip5')
zip5_element.text = self.zip5
address.append(zip5_element)
zip4_element = ET.Element('Zip4')
zip4_element.text = self.zip4
address.append(zip4_element)
root.append(address)
return ET.tostring(root)
def main():
'''It's the freaking main function, PEP8!!!! Stop complaining!'''
saturday_night_live = USPSAddress(
name='NBC Studios',
street='30 Rockefeller Plaza', suite='Suite 8h',
city='new york', state='new york', zip5='10112')
print saturday_night_live.original()
print "---"
print saturday_night_live.standardized()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment