diff --git a/.gitignore b/.gitignore index 519577a..cbf9733 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ run.py .idea *.egg-info/ -dist/ \ No newline at end of file +dist/ +/bin/ +/lib/ +/pyvenv.cfg diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..315b3d7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +lxml==4.9.1 +xmltodict==0.13.0 diff --git a/setup.py b/setup.py index e1bf475..b2a09fd 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = '0.2.0' +VERSION = '0.3.0' LONG_DESC = """\ A python wrapper to the USPS api, currently only supports address validation """ @@ -13,7 +13,6 @@ long_description=LONG_DESC, classifiers=[ 'Programming Language :: Python', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Operating System :: OS Independent', 'Natural Language :: English', @@ -31,6 +30,7 @@ packages=find_packages(), zip_safe=False, install_requires=[ + 'lxml', + 'xmltodict' ], - ) diff --git a/tests.py b/tests.py index b0b0f5e..9428124 100644 --- a/tests.py +++ b/tests.py @@ -1,32 +1,56 @@ +import os import unittest -from xml.etree import ElementTree as ET +import random +from datetime import datetime, timedelta from xml.dom import minidom -from usps.addressinformation import * -USERID = None +from xml.etree import ElementTree + import xmltodict -import json + from constants import CARRIER_PICKUP_SCHEDULE_REQUEST_SERVICE_TYPE +from usps.addressinformation import * -USERID = "" +USERID = os.environ.get('USERID') # A user id must be defined in the environment variables to run the test USPS_CONNECTION_TEST = 'https://secure.shippingapis.com/ShippingAPITest.dll' USPS_CONNECTION_HTTP = 'http://production.shippingapis.com/ShippingAPI.dll' + + +# USPS cares about the order of the arbitrary order that the data submitted, but normal users shouldn't. +# Let's shuffle the data to ensure that it can be inputted in any order. +def randomize_dict(obj): + # Python 3.7+ maintains ordering in dicts + randomized = {} + keys = list(obj.keys()) + random.shuffle(keys) + for key in keys: + if isinstance(obj[key], dict): + randomized[key] = randomize_dict(obj[key]) + else: + randomized[key] = obj[key] + + return randomized + + +def print_xml(xml): + print(minidom.parseString(ElementTree.tostring(xml)).toprettyxml(indent=" ")) + class TestAddressInformationAPI(unittest.TestCase): def test_validate_address(self): + self.assertIsNotNone(USERID) address_validation = Address(user_id=USERID, url=USPS_CONNECTION_TEST) response = address_validation.validate(address2='760 Charcot Ave', city='San Jose', state='CA') - print(response) - # print(minidom.parseString(ET.tostring(response_xml)).toprettyxml(indent=" ")) - def test_package_makexml(self): + def test_domestic_rate_make_xml(self): """ test rate Returns: """ + self.assertIsNotNone(USERID) rate = DomesticRate(user_id=USERID, url=USPS_CONNECTION_TEST) - package_dict_one = { + package_dict_one = randomize_dict({ 'Service': 'FIRST CLASS', 'FirstClassMailType': 'LETTER', 'ZipOrigination': 44106, @@ -36,9 +60,8 @@ def test_package_makexml(self): 'Container': '', 'Size': 'REGULAR', 'Machinable': True - } - - package_dict_two = { + }) + package_dict_two = randomize_dict({ 'Service': 'PRIORITY', 'ZipOrigination': 44106, 'ZipDestination': 20770, @@ -58,11 +81,11 @@ def test_package_makexml(self): { 'SpecialService': 2 }] - } + }) + xml = rate.make_xml(package_dicts=[package_dict_one, package_dict_two]) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - def test_make_rate_request(self): + def test_domestic_rate_submit(self): """ test rate xml construction and api call Returns: @@ -70,26 +93,24 @@ def test_make_rate_request(self): """ rate = DomesticRate(user_id=USERID, url=USPS_CONNECTION_TEST) - package_dict_one = { + package_dict_one = randomize_dict({ 'Service': 'FIRST CLASS', 'FirstClassMailType': 'LETTER', 'ZipOrigination': 44106, 'ZipDestination': 20770, 'Pounds': 0, - 'Ounces': float(3.12345678), + 'Ounces': 3.12345678, 'Container': 'VARIABLE', - 'Size': 'REGULAR', 'Machinable': True - } + }) - package_dict_two = { + package_dict_two = randomize_dict({ 'Service': 'PRIORITY', 'ZipOrigination': 44106, 'ZipDestination': 20770, 'Pounds': 1, 'Ounces': 8, 'Container': 'NONRECTANGULAR', - 'Size': 'LARGE', 'Width': 15, 'Length': 30, 'Height': 15, @@ -107,19 +128,17 @@ def test_make_rate_request(self): 'ContentType': 'LIVES', 'ContentDescription': 'Other' } - } + }) xml = rate.make_xml(package_dicts=[package_dict_one, package_dict_two]) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) response_xml = rate.submit_xml(xml) - print(minidom.parseString(ET.tostring(response_xml)).toprettyxml(indent=" ")) - return_dict = xmltodict.parse(ET.tostring(response_xml)) - print(json.loads(json.dumps(return_dict))) - + return_dict = xmltodict.parse(ElementTree.tostring(response_xml)) - def test_intelRate(self): + self.assertTrue('RateV4Response' in return_dict and 'Package' in return_dict.get('RateV4Response')) + self.assertTrue(len(return_dict['RateV4Response']['Package']) == 2) - package_dict_two = { + def test_intel_rate_v2_submit(self): + package_dict_two = randomize_dict({ 'Pounds': 1, 'Ounces': 8, 'MailType': 'Package', @@ -141,41 +160,37 @@ def test_intelRate(self): { 'ExtraService': 106 } - ], + ], 'Content': { 'ContentType': 'Documents', 'ContentDescription': 'Other' } - } + }) + + self.assertIsNotNone(USERID) intl = IntlRateV2(user_id=USERID, ) xml = intl.make_xml(package_dicts=[package_dict_two]) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) response_xml = intl.submit_xml(xml) - print(intl.to_json(response_xml)) - - def test_tracker(self): - """ - test tracker - Returns: - """ - tracker = Track(user_id="766POSTG6978",url=USPS_CONNECTION_TEST) + def test_tracker_submit(self): + tracker = Track(user_id="766POSTG6978", url=USPS_CONNECTION_TEST) tracker_ids = ['9405536897846333893331'] xml = tracker.make_xml(tracker_ids=tracker_ids) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - response_xml = tracker.submit_xml(xml) - print(minidom.parseString(ET.tostring(response_xml)).toprettyxml(indent=" ")) - def test_carrier_pickup_availability(self): - """ - test carrier pickup availability - Returns: + # USPS doesn't seem to offer sandbox or testing tracking numbers so lets just check against a + # valid not found response + with self.assertRaises(USPSXMLError) as context: + tracker.submit_xml(xml) - """ - avail = CarrierPickupAvailability(user_id='766POSTG6978',url=USPS_CONNECTION_TEST) - request_dict = { + self.assertTrue( + 'A status update is not yet available on your Priority Mail' in context.exception.info.get('Description')) + + def test_carrier_pickup_availability_submit(self): + self.assertIsNotNone(USERID) + avail = CarrierPickupAvailability(user_id=USERID, url=USPS_CONNECTION_TEST) + request_dict = randomize_dict({ "FirmName": "PostGround Corp", "SuiteOrApt": "Suite777", "Address2": "760 Charcot Ave", @@ -184,23 +199,16 @@ def test_carrier_pickup_availability(self): "State": "CA", "ZIP5": "95131", "ZIP4": "2223" - } + }) xml = avail.make_xml( pickup_availability_dict=request_dict ) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) response_xml = avail.submit_xml(xml) - print(avail.to_json(response_xml)) - def test_carrier_pickup_schedule(self): - """ - - No test api - Returns: - - """ + def test_carrier_pickup_schedule_submit(self): + self.assertIsNotNone(USERID) schedule = CarrierPickupSchedule(user_id=USERID, url=USPS_CONNECTION_TEST) - request_dict = { + request_dict = randomize_dict({ "FirstName": "Luyi", "LastName": "Doe", "FirmName": "PostGround Corp", @@ -226,20 +234,14 @@ def test_carrier_pickup_schedule(self): "EstimatedWeight": "14", "PackageLocation": "Front Door", "SpecialInstructions": "Behind the screen door" - } + }) xml = schedule.make_xml(pickup_schedule_dict=request_dict) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) response_xml = schedule.submit_xml(xml) - print(schedule.to_json(response_xml)) - def test_carrier_pickup_cancel(self): - """ - No test api - Returns: - - """ + def test_carrier_pickup_cancel_make_xml(self): + self.assertIsNotNone(USERID) pickup_cancel = CarrierPickupCancel(user_id=USERID, url=USPS_CONNECTION_TEST) - request_dict = { + request_dict = randomize_dict({ "FirmName": "PostGround Corp", "SuiteOrApt": "Suite777", "Address2": "760 Charcot Ave", @@ -249,19 +251,14 @@ def test_carrier_pickup_cancel(self): "ZIP5": "95131", "ZIP4": "2223", "ConfirmationNumber": "XXXXXX" - } + }) xml = pickup_cancel.make_xml(pickup_cancel_dict=request_dict) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - - def test_carrier_pickup_change(self): - """ - - No test api - Returns: - """ + def test_carrier_pickup_change_make_xml(self): + # https://www.usps.com/business/web-tools-apis/service-delivery-calculator-get-locations-api.htm + self.assertIsNotNone(USERID) schedule = CarrierPickupChange(user_id=USERID, url=USPS_CONNECTION_TEST) - request_dict = { + request_dict = randomize_dict({ "FirstName": "Luyi", "LastName": "Doe", "FirmName": "PostGround Corp", @@ -287,41 +284,35 @@ def test_carrier_pickup_change(self): "EstimatedWeight": "14", "PackageLocation": "Front Door", "SpecialInstructions": "Behind the screen door", - "ConfirmationNumber" :"xxxx" + "ConfirmationNumber": "xxxx" - } + }) xml = schedule.make_xml(pickup_schedule_change_dict=request_dict) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - - def test_mail_service(self): - SERVICE_NAME = ['PriorityMail', 'StandardB', 'FirstClassMail', 'ExpressMailCommitment'] + def test_mail_services_make_xml(self): + self.assertIsNotNone(USERID) mail_service = MailService(user_id=USERID, url=USPS_CONNECTION_TEST) - request_dict ={ + request_dict = randomize_dict({ "OriginZip": "95131", "DestinationZip": "21114" - } - for service_name in SERVICE_NAME: - xml = mail_service.make_xml(mail_service_dict=request_dict,service_name=service_name) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - - def test_sdcgetlocation(self): - sdcgl = ServiceDelivery(user_id=USERID, url=USPS_CONNECTION_HTTP) - request_dict = { + }) + for service_name in MailService.SERVICE_NAMES: + xml = mail_service.make_xml(mail_service_dict=request_dict, service_name=service_name) + + def test_sdc_get_location(self): + self.assertIsNotNone(USERID) + service_delivery = ServiceDelivery(user_id=USERID, url=USPS_CONNECTION_HTTP) + request_dict = randomize_dict({ "MailClass": "0", "OriginZIP": "70601", "DestinationZIP": "98101", - "AcceptDate": "30-April-2018", + "AcceptDate": (datetime.now() + timedelta(days=1)).strftime('%d-%B-%Y'), "AcceptTime": "0900", "NonEMDetail": "True", - } - xml = sdcgl.make_xml(sdc_get_location_dict=request_dict) - print(minidom.parseString(ET.tostring(xml)).toprettyxml(indent=" ")) - response_xml = sdcgl.submit_xml(xml) - print(sdcgl.to_json(response_xml)) + }) + xml = service_delivery.make_xml(sdc_get_location_dict=request_dict) + response_xml = service_delivery.submit_xml(xml) if __name__ == '__main__': - #please append your USPS USERID to test against the wire - import sys unittest.main() diff --git a/usps/addressinformation/base.py b/usps/addressinformation/base.py index eb13db2..a5088f9 100644 --- a/usps/addressinformation/base.py +++ b/usps/addressinformation/base.py @@ -2,18 +2,15 @@ See https://www.usps.com/business/web-tools-apis/Address-Information-v3-2.htm for complete documentation of the API ''' +import html import json import xmltodict as XTD -try: - from urllib.parse import urlencode -except ImportError: - from urllib import urlencode -try: - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen +from urllib.parse import urlencode +from urllib.request import urlopen + +from lxml import etree +from lxml.etree import SubElement, Element -from lxml import etree as ET def utf8urlencode(data): ret = dict() @@ -26,21 +23,21 @@ def utf8urlencode(data): def dicttoxml(dictionary, parent, tagname, attributes=None): - element = ET.SubElement(parent, tagname) - if attributes: #USPS likes things in a certain order! + element = SubElement(parent, tagname) + if attributes: # USPS likes things in a certain order! for key in attributes: if key in dictionary: - ET.SubElement(element, key).text = str(dictionary.get(key, '')) + SubElement(element, key).text = str(dictionary.get(key, '')) else: for key, value in dictionary.items(): - ET.SubElement(element, key).text = value + SubElement(element, key).text = value return element def xmltodict(element): ret = dict() - for item in element.getchildren(): - ret[item.tag] = item.text + for item in element: + ret[item.tag] = item.text and html.unescape(item.text) or None return ret @@ -60,10 +57,10 @@ def __init__(self, url='https://secure.shippingapis.com/ShippingAPI.dll'): self.url = url def submit_xml(self, xml): - data = {'XML': ET.tostring(xml), + data = {'XML': etree.tostring(xml), 'API': self.API} response = urlopen(self.url, utf8urlencode(data)) - root = ET.parse(response).getroot() + root = etree.parse(response).getroot() if root.tag == 'Error': raise USPSXMLError(root) error = root.find('.//Error') @@ -74,25 +71,27 @@ def submit_xml(self, xml): @staticmethod def parse_xml(xml): items = list() - for item in xml.getchildren(): + for item in xml: items.append(xmltodict(item)) return items - def execute(self, userid, object_dicts): xml = self.make_xml(userid, object_dicts) return self.parse_xml(self.submit_xml(xml)) def to_json(self, xml): - return_dict = XTD.parse(ET.tostring(xml)) + return_dict = XTD.parse(etree.tostring(xml)) return json.loads(json.dumps(return_dict)) + def make_xml(self, *args): + # This should be implemented on base classes + pass + class Address(USPSService): """ Base Address class. - - Address Formating Information from the USPS. + Address Formatting Information from the USPS. FirmName - Name of Business (XYZ Corp.) [Optional] Address1 - Apartment or Suite number. [Optional] @@ -173,7 +172,7 @@ def validate(self, firm_name='', address1='', address2='', city='', state='', zi return self.format_response(valid_address[0], title_case) def make_xml(self, userid, addresses): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = userid index = 0 for address_dict in addresses: @@ -186,34 +185,53 @@ def make_xml(self, userid, addresses): #######################Rate API ############################################################# class DomesticRate(USPSService): + # https://www.usps.com/business/web-tools-apis/rate-calculator-api.htm#_Toc114840147 SERVICE_NAME = 'RateV4' API = 'RateV4' USER_ID = '' PACKAGE_CHILD_XML_NAME = 'Package' - PACKAGE_PARAMETERS = ['Service', # required - 'FirstClassMailType',# optional # Required when Service == FIRST CLASS or FISRT CLASS COMMERICAL or FIRST CLASS HFP COMMERCIAL - 'ZipOrigination', # required - 'ZipDestination', # required - 'Pounds', # required - 'Ounces', # required - 'Container', # required - 'Size', # required - 'Width', # optional - 'Length', # optional - 'Height', # optional - 'Girth', # optional - 'Value', # optional - 'AmountToCollect', # optional - # 'SpecialServices', # object # optional - # 'Content', # object # optional - 'GroundOnly', # optional - 'SortBy', # optional - 'Machinable', # optional - 'ReturnLocations', # optional - 'ReturnServiceInfo', # optional - 'DropOffTime', # optional - 'ShipDate'] # optional + PACKAGE_PARAMETERS = [ + 'Service', + 'FirstClassMailType', # Required when Service == FIRST CLASS or FISRT CLASS COMMERICAL or FIRST CLASS HFP COMMERCIAL + 'ZipOrigination', + 'ZipDestination', + 'Pounds', + 'Ounces', + 'Container', + 'Width', + 'Length', + 'Height', + 'Girth', + 'Value', + 'AmountToCollect', + 'SpecialServices', + 'Content', + 'GroundOnly', + 'SortBy', + 'Machinable', + 'ReturnLocations', + 'ReturnServiceInfo', + 'DropOffTime', + 'ShipDate' + ] + OPTIONAL_PARAMETERS = [ + 'FirstClassMailType', + 'Width', + 'Length', + 'Height', + 'Girth', + 'Value', + 'AmountToCollect', + 'GroundOnly', + 'SortBy', + 'Machinable', + 'ReturnLocations', + 'ReturnServiceInfo', + 'DropOffTime', + 'ShipDate' + ] + SPECIAL_SERVICE_CHILD_XML_NAME = 'SpecialServices' SPECIAL_SERVICE_PARAMETERS = ['SpecialService'] @@ -226,56 +244,80 @@ def __init__(self, user_id, *args, **kwargs): self.USER_ID = user_id def make_xml(self, package_dicts): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID - ET.SubElement(root, "Revision").text = str(2) + SubElement(root, "Revision").text = '2' index = 0 for package_dict in package_dicts: - package_xml = dicttoxml(package_dict, root, self.PACKAGE_CHILD_XML_NAME, self.PACKAGE_PARAMETERS) + package_xml = SubElement(root, self.PACKAGE_CHILD_XML_NAME) + package_xml.attrib['ID'] = str(index) + index += 1 - # multiple special services - if package_dict.get(self.SPECIAL_SERVICE_CHILD_XML_NAME): - element = ET.SubElement(package_xml, self.SPECIAL_SERVICE_CHILD_XML_NAME) - for special_service in package_dict.get(self.SPECIAL_SERVICE_CHILD_XML_NAME): - for key in self.SPECIAL_SERVICE_PARAMETERS: - if key in special_service: - ET.SubElement(element, key).text = str(special_service.get(key, '')) + for param in self.PACKAGE_PARAMETERS: + content = package_dict.get(param) - # content is not an array. only one as child xml - if package_dict.get(self.CONTENT_CHILD_XML_NAME): - content = package_dict.get(self.CONTENT_CHILD_XML_NAME) - content_xml = dicttoxml(content, package_xml, self.CONTENT_CHILD_XML_NAME, self.CONTENT_PARAMETERS) + if content is None: + continue + + if param == self.SPECIAL_SERVICE_CHILD_XML_NAME: + element = SubElement(package_xml, self.SPECIAL_SERVICE_CHILD_XML_NAME) + for special_service in content: + for key in self.SPECIAL_SERVICE_PARAMETERS: + if special_service.get(key): + SubElement(element, key).text = str(special_service.get(key, '')) + + elif param == self.CONTENT_CHILD_XML_NAME: + dicttoxml(content, package_xml, self.CONTENT_CHILD_XML_NAME, self.CONTENT_PARAMETERS) + + else: + SubElement(package_xml, param).text = str(package_dict.get(param, '')) - package_xml.attrib['ID'] = str(index) - index += 1 return root + class IntlRateV2(USPSService): + # https://www.usps.com/business/web-tools-apis/rate-calculator-api.htm + SERVICE_NAME = "IntlRateV2" API = "IntlRateV2" USER_ID = "" PACKAGE_CHILD_XML_NAME = 'Package' PACKAGE_PARAMETERS = [ - 'Pounds', # required - 'Ounces', # required - 'MailType', # required ,same as service in domestic - 'Machinable', # optional - # 'GXG' #optinal - 'ValueOfContents', # required - 'Country', # required - 'Container', # required - 'Size', # required - 'Width', # optional - 'Length', # optional - 'Height', # optional - 'Girth', # optional - 'OriginZip', # optional - 'CommercialFlag', # optional - 'CommercialPlusFlag', # optional - # 'ExtraServices', # object # optional - # 'Content', # object # optional - 'AcceptanceDateTime', # optional - 'DestinationPostalCode', # optional + 'Pounds', + 'Ounces', + 'MailType', + 'Machinable', + 'GXG', + 'ValueOfContents', + 'Country', + 'Container', + 'Size', + 'Width', + 'Length', + 'Height', + 'Girth', + 'OriginZip', + 'CommercialFlag', + 'CommercialPlusFlag', + 'ExtraServices', + 'Content', + 'AcceptanceDateTime', + 'DestinationPostalCode', + ] + OPTIONAL_PARAMETERS = [ + 'Machinable', + 'GXG', + 'Width', + 'Length', + 'Height', + 'Girth', + 'OriginZip', + 'CommercialFlag', + 'CommercialPlusFlag', + 'ExtraServices', + 'Content', + 'AcceptanceDateTime', + 'DestinationPostalCode' ] GXG_CHILD_XML_NAME = 'GXG' @@ -285,40 +327,45 @@ class IntlRateV2(USPSService): EXTRA_SERVICE_PARAMETERS = ['ExtraService'] CONTENT_CHILD_XML_NAME = 'Content' - CONTENT_PARAMETERS = ['ContentType', - 'ContentDescription'] + CONTENT_PARAMETERS = ['ContentType', 'ContentDescription'] def __init__(self, user_id, *args, **kwargs): super(IntlRateV2, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, package_dicts): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID - ET.SubElement(root, "Revision").text = str(2) + SubElement(root, "Revision").text = str(2) index = 0 + for package_dict in package_dicts: - package_xml = dicttoxml(package_dict, root, self.PACKAGE_CHILD_XML_NAME, self.PACKAGE_PARAMETERS) + package_xml = SubElement(root, self.PACKAGE_CHILD_XML_NAME) + package_xml.attrib['ID'] = str(index) + index += 1 - # multiple special services - if package_dict.get(self.EXTRA_SERVICE_CHILD_XML_NAME): - element = ET.SubElement(package_xml, self.EXTRA_SERVICE_CHILD_XML_NAME) - for special_service in package_dict.get(self.EXTRA_SERVICE_CHILD_XML_NAME): - for key in self.EXTRA_SERVICE_PARAMETERS: - if key in special_service: - ET.SubElement(element, key).text = str(special_service.get(key, '')) + for param in self.PACKAGE_PARAMETERS: + content = package_dict.get(param) - # content is not an array. only one as child xml - if package_dict.get(self.CONTENT_CHILD_XML_NAME): - content = package_dict.get(self.CONTENT_CHILD_XML_NAME) - content_xml = dicttoxml(content, package_xml, self.CONTENT_CHILD_XML_NAME, self.CONTENT_PARAMETERS) + if content is None: + continue - if package_dict.get(self.GXG_CHILD_XML_NAME): - content = package_dict.get(self.GXG_CHILD_XML_NAME) - content_xml = dicttoxml(content, package_xml, self.GXG_CHILD_XML_NAME, self.GXG_PARAMETERS) + if param == self.EXTRA_SERVICE_CHILD_XML_NAME: + service_element = SubElement(package_xml, self.EXTRA_SERVICE_CHILD_XML_NAME) + + for extra_service in content: + for service_key in self.EXTRA_SERVICE_PARAMETERS: + SubElement(service_element, service_key).text = str(extra_service.get(service_key, '')) + + elif param == self.CONTENT_CHILD_XML_NAME: + dicttoxml(content, package_xml, self.CONTENT_CHILD_XML_NAME, self.CONTENT_PARAMETERS) + + elif param == self.GXG_CHILD_XML_NAME: + dicttoxml(content, package_xml, self.GXG_CHILD_XML_NAME, self.GXG_PARAMETERS) + + else: + SubElement(package_xml, param).text = str(package_dict.get(param, '')) - package_xml.attrib['ID'] = str(index) - index += 1 return root @@ -336,15 +383,14 @@ def __init__(self, user_id, *args, **kwargs): self.USER_ID = user_id def make_xml(self, tracker_ids): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID for tracker in tracker_ids: - element = ET.SubElement(root, self.TRACK_CHILD_XML_NAME) + element = SubElement(root, self.TRACK_CHILD_XML_NAME) element.attrib['ID'] = tracker return root - ######################## PACKAGE PICKUP API ########################### @@ -352,68 +398,79 @@ class CarrierPickupAvailability(USPSService): SERVICE_NAME = 'CarrierPickupAvailability' API = 'CarrierPickupAvailability' USER_ID = '' - CARRIER_PICKUP_AVAILABILITY_PARAMETERS = ['FirmName', - 'SuiteOrApt', - 'Address2', - 'Urbanization', - 'City', - 'State', - 'ZIP5', - 'ZIP4', - 'Date'] + CARRIER_PICKUP_AVAILABILITY_PARAMETERS = [ + 'FirmName', + 'SuiteOrApt', + 'Address2', + 'Urbanization', + 'City', + 'State', + 'ZIP5', + 'ZIP4', + 'Date' + ] def __init__(self, user_id, *args, **kwargs): super(CarrierPickupAvailability, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, pickup_availability_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID - for key, value in pickup_availability_dict.items(): - if key in self.CARRIER_PICKUP_AVAILABILITY_PARAMETERS: - ET.SubElement(root, key).text = value + + for key in self.CARRIER_PICKUP_AVAILABILITY_PARAMETERS: + SubElement(root, key).text = pickup_availability_dict.get(key, '') + return root class CarrierPickupSchedule(USPSService): + # https://www.usps.com/business/web-tools-apis/package-pickup-api.htm SERVICE_NAME = 'CarrierPickupSchedule' API = "CarrierPickupSchedule" USER_ID = '' - CARRIER_PICKUP_SCHEDULE = ["FirstName", - "LastName", - "FirmName", - "SuiteOrApt", - "Address2", - "Urbanization", - "City", - "State", - "ZIP5", - "ZIP4", - "Phone", - "Extension", - "EstimatedWeight", - "PackageLocation", - "SpecialInstructions", - "EmailAddress"] - - CARRIER_PICKUP_SCHEDULE_PACKAGE=["ServiceType","Count"] + CARRIER_PICKUP_SCHEDULE = [ + 'FirstName', + 'LastName', + 'FirmName', + 'SuiteOrApt', + 'Address2', + 'Urbanization', + 'City', + 'State', + 'ZIP5', + 'ZIP4', + 'Phone', + 'Extension', + 'Package', + 'EstimatedWeight', + 'PackageLocation', + 'SpecialInstructions', + 'EmailAddress' + ] + + CARRIER_PICKUP_SCHEDULE_PACKAGE = ["ServiceType", "Count"] PACKAGE_KEY = "Package" + def __init__(self, user_id, *args, **kwargs): super(CarrierPickupSchedule, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, pickup_schedule_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID + if self.PACKAGE_KEY not in pickup_schedule_dict: print("Package is needed") return - for key, value in pickup_schedule_dict.items(): - if key in self.CARRIER_PICKUP_SCHEDULE: - ET.SubElement(root, key).text = value - for package_item_dict in pickup_schedule_dict.get('Package'): - dicttoxml(package_item_dict, root, self.PACKAGE_KEY, self.CARRIER_PICKUP_SCHEDULE_PACKAGE) + for key in self.CARRIER_PICKUP_SCHEDULE: + if key == 'Package': + for package_item_dict in pickup_schedule_dict.get('Package'): + dicttoxml(package_item_dict, root, self.PACKAGE_KEY, self.CARRIER_PICKUP_SCHEDULE_PACKAGE) + else: + SubElement(root, key).text = pickup_schedule_dict.get(key, '') + return root @@ -421,26 +478,28 @@ class CarrierPickupCancel(USPSService): SERVICE_NAME = 'CarrierPickupCancel' API = 'CarrierPickupCancel' USER_ID = '' - CARRIER_PICKUP_CANCEL_PARAMETERS= ['FirmName', - 'SuiteOrApt', - 'Address2', - 'Urbanization', - 'City', - 'State', - 'ZIP5', - 'ZIP4', - 'ConfirmationNumber'] + CARRIER_PICKUP_CANCEL_PARAMETERS = [ + 'FirmName', + 'SuiteOrApt', + 'Address2', + 'Urbanization', + 'City', + 'State', + 'ZIP5', + 'ZIP4', + 'ConfirmationNumber' + ] def __init__(self, user_id, *args, **kwargs): super(CarrierPickupCancel, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, pickup_cancel_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID for key, value in pickup_cancel_dict.items(): if key in self.CARRIER_PICKUP_CANCEL_PARAMETERS: - ET.SubElement(root, key).text = value + SubElement(root, key).text = value return root @@ -448,40 +507,42 @@ class CarrierPickupChange(USPSService): SERVICE_NAME = "CarrierPickupChange" API = "CarrierPickupChange" USER_ID = '' - CARRIER_PICKUP_SCHEDULE = ["FirstName", - "LastName", - "FirmName", - "SuiteOrApt", - "Address2", - "Urbanization", - "City", - "State", - "ZIP5", - "ZIP4", - "Phone", - "Extension", - "EstimatedWeight", - "PackageLocation", - "SpecialInstructions", - "ConfirmationNumber", - "EmailAddress"] + CARRIER_PICKUP_SCHEDULE = [ + 'FirstName', + 'LastName', + 'FirmName', + 'SuiteOrApt', + 'Address2', + 'Urbanization', + 'City', + 'State', + 'ZIP5', + 'ZIP4', + 'Phone', + 'Extension', + 'EstimatedWeight', + 'PackageLocation', + 'SpecialInstructions', + 'ConfirmationNumber', + 'EmailAddress' + ] - CARRIER_PICKUP_SCHEDULE_PACKAGE = ["ServiceType", "Count"] - PACKAGE_KEY = "Package" + CARRIER_PICKUP_SCHEDULE_PACKAGE = ['ServiceType', 'Count'] + PACKAGE_KEY = 'Package' def __init__(self, user_id, *args, **kwargs): super(CarrierPickupChange, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, pickup_schedule_change_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID if self.PACKAGE_KEY not in pickup_schedule_change_dict: print("Package is needed") return for key, value in pickup_schedule_change_dict.items(): if key in self.CARRIER_PICKUP_SCHEDULE: - ET.SubElement(root, key).text = value + SubElement(root, key).text = value for package_item_dict in pickup_schedule_change_dict.get('Package'): dicttoxml(package_item_dict, root, self.PACKAGE_KEY, self.CARRIER_PICKUP_SCHEDULE_PACKAGE) @@ -507,49 +568,66 @@ def __init__(self, user_id, *args, **kwargs): self.USER_ID = user_id def make_xml(self, pickup_inquiry_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID for key, value in pickup_inquiry_dict.items(): if key in self.CARRIER_PICKUP_CANCEL_PARAMETERS: - ET.SubElement(root, key).text = value + SubElement(root, key).text = value return root ################################# Service Standard Service ################################ class MailService(USPSService): - SERVICE_NAME = ['PriorityMail', 'StandardB', 'FirstClassMail', 'ExpressMailCommitment'] - API = SERVICE_NAME - MAIL_SERVICE_PARAMETERS = ['OriginZip', 'DestinationZip', 'DestinationType'] + SERVICE_NAMES = [ + 'PriorityMail', + 'StandardB', + 'FirstClassMail', + 'ExpressMailCommitment' + ] + API = SERVICE_NAMES + MAIL_SERVICE_PARAMETERS = [ + 'OriginZip', + 'DestinationZip', + 'DestinationType' + ] MAIL_SERVICE_PRIORITY_PARAMETERS = MAIL_SERVICE_PARAMETERS + ['PMGuarantee', 'ClientType'] MAIL_SERVICE_STANDARDB_PARAMETERS = MAIL_SERVICE_PARAMETERS + ['ClientType'] MAIL_SERVICE_FIRSTCLASS_PARAMETERS = MAIL_SERVICE_PARAMETERS - MAIL_SERVICE_EXPRESS_PARAMETERS = ['OriginZip', 'DestinationZip', 'Date', 'DropOffTime' 'PMGuarantee', 'ReturnDates'] + MAIL_SERVICE_EXPRESS_PARAMETERS = [ + 'OriginZip', + 'DestinationZip', + 'Date', + 'DropOffTime' + 'PMGuarantee', + 'ReturnDates' + ] def __init__(self, user_id, *args, **kwargs): super(MailService, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, mail_service_dict, service_name): - if service_name in self.SERVICE_NAME: - root = ET.Element(service_name + 'Request') + if service_name in self.SERVICE_NAMES: + root = Element(service_name + 'Request') root.attrib['USERID'] = self.USER_ID for key, value in mail_service_dict.items(): - if service_name is 'ExpressMailCommitment': + if service_name == 'ExpressMailCommitment': parameters = self.MAIL_SERVICE_EXPRESS_PARAMETERS - elif service_name is 'PriorityMail': + elif service_name == 'PriorityMail': parameters = self.MAIL_SERVICE_PRIORITY_PARAMETERS - elif service_name is 'StandardB': + elif service_name == 'StandardB': parameters = self.MAIL_SERVICE_STANDARDB_PARAMETERS - elif service_name is 'FirstClassMail': + elif service_name == 'FirstClassMail': parameters = self.MAIL_SERVICE_FIRSTCLASS_PARAMETERS else: parameters = self.MAIL_SERVICE_PARAMETERS if key in parameters: - ET.SubElement(root, key).text = value + SubElement(root, key).text = value return root + class ServiceDelivery(USPSService): SERVICE_NAME = 'SDCGetLocations' API = SERVICE_NAME @@ -563,17 +641,28 @@ class ServiceDelivery(USPSService): "NonEMDetail", "NonEMOriginType", "NonEMDestType", - "Weight", - "ClientType"] + "Weight" + ] + OPTIONAL_PARAMETERS = [ + "AcceptDate", + "AcceptTime", + "NonEMDetail", + "NonEMOriginType", + "NonEMDestType", + "Weight" + ] def __init__(self, user_id, *args, **kwargs): super(ServiceDelivery, self).__init__(*args, **kwargs) self.USER_ID = user_id def make_xml(self, sdc_get_location_dict): - root = ET.Element(self.SERVICE_NAME + 'Request') + root = Element(self.SERVICE_NAME + 'Request') root.attrib['USERID'] = self.USER_ID - for key, value in sdc_get_location_dict.items(): - if key in self.SERVICE_DELIVERY_PARAMETERS: - ET.SubElement(root, key).text = value + + for key in self.SERVICE_DELIVERY_PARAMETERS: + if sdc_get_location_dict.get(key): + SubElement(root, key).text = str(sdc_get_location_dict.get(key)) + return root +