Last active
July 14, 2016 16:04
-
-
Save dgrant/5386253 to your computer and use it in GitHub Desktop.
Finds Canada Post rates to send a parcel or oversized/non-standard envelope domestically, to the US, or internationally. Particularly useful for people selling items on eBay. It will print our rates to different destinations around the country/US/world so you can get a feel for the min/average/max rate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import base64 | |
import xml.etree.ElementTree as ET | |
import urllib.request, urllib.error, urllib.parse | |
import operator | |
import time | |
from canadapostconf import username, password | |
NS = '{http://www.canadapost.ca/ws/ship/rate-v2}' | |
RATE_POST = """ | |
<mailing-scenario xmlns="http://www.canadapost.ca/ws/ship/rate-v2"> | |
<quote-type>counter</quote-type> | |
<parcel-characteristics> | |
<weight>%(weight)f</weight> | |
<dimensions> | |
<length>%(length)f</length> | |
<width>%(width)f</width> | |
<height>%(height)f</height> | |
</dimensions> | |
</parcel-characteristics> | |
<origin-postal-code>%(origin)s</origin-postal-code> | |
<destination> | |
</destination> | |
</mailing-scenario> | |
""" | |
CODES = { | |
'united-states': | |
('zip-code', { | |
'Florida': '32792', | |
'Washington': '98576', | |
'California': '93215', | |
'Maine': '04453', | |
'Kansas': '67425', | |
}), | |
'international': | |
('country-code', { | |
'Great Britain': 'GB', | |
'Hungary': 'HU', | |
'Russia': 'RU', | |
'China': 'CN', | |
'Australia': 'AU', | |
'Brazil': 'BR', | |
}), | |
'domestic': | |
('postal-code', { | |
'Vancouver, BC': 'V5K0A1', | |
'St. John\'s, NL': 'A1A0A1', | |
'Toronto, ON': 'M3H6A7', | |
'Rainbox Lake, AB': 'T0H2Y0', | |
'Churchill, MB': 'R0B0E0', | |
'Rouyn-Noranda, QC': 'J9X1A1', | |
}), | |
} | |
def get_letter_rate(weight, destination_type): | |
if destination_type == 'domestic': | |
if weight <= 0.1: | |
rate = 1.80 | |
elif weight <= 0.2: | |
rate = 2.95 | |
elif weight <= 0.3: | |
rate = 4.10 | |
elif weight <= 0.4: | |
rate = 4.70 | |
elif weight <= 0.5: | |
rate = 5.05 | |
elif destination_type == 'united-states': | |
if weight <= 0.1: | |
rate = 2.95 | |
elif weight <= 0.2: | |
rate = 5.15 | |
elif weight <= 0.5: | |
rate = 10.30 | |
elif destination_type == 'international': | |
if weight <= 0.1: | |
rate = 5.90 | |
elif weight <= 0.2: | |
rate = 10.30 | |
elif weight <= 0.5: | |
rate = 20.60 | |
return rate | |
def get_rate_quote(weight, length, width, height, origin, destination_type, code): | |
if length < width: | |
raise Exception('length cannot be less than width') | |
if width < height: | |
raise Exception('width cannot be less than height') | |
if 14 <= length and length <= 38 \ | |
and 9 <= width and width <= 27 \ | |
and 0 <= height and height <= 2: | |
print("*** Non-standard and Oversize Lettermail") | |
rate = get_letter_rate(weight, destination_type) | |
return [(rate, 'oversize and non-standard lettermail')] | |
post = RATE_POST % {'weight': weight, 'length': length, 'width': width, 'height': height, | |
'origin': origin} | |
root = ET.fromstring(post) | |
destination_element = root.find(NS + 'destination') | |
destination_type_elem = ET.Element(NS + destination_type) | |
destination_code_elem = ET.Element(NS + CODES[destination_type][0]) | |
destination_code_elem.text = code | |
destination_type_elem.append(destination_code_elem) | |
destination_element.append(destination_type_elem) | |
post = ET.tostring(root) | |
base64auth = base64.encodestring(('%s:%s' % (username, password)).encode())[:-1].decode() | |
headers = { | |
'Accept': 'application/vnd.cpc.ship.rate-v2+xml', | |
'Content-Type': 'application/vnd.cpc.ship.rate-v2+xml', | |
'Accept-language': 'en-CA', | |
'Authorization': 'Basic %s' % base64auth | |
} | |
req = urllib.request.Request('https://soa-gw.canadapost.ca/rs/ship/price', post, | |
headers=headers) | |
try: | |
response = urllib.request.urlopen(req).read() | |
except urllib.error.HTTPError: | |
print("Error making request:", post) | |
print("response=", response) | |
raise | |
time.sleep(1) | |
quotes = [] | |
root = ET.fromstring(response) | |
price_quotes = root.findall(NS + 'price-quote') | |
for price_quote in price_quotes: | |
service_name = price_quote.find(NS + 'service-name').text | |
price = price_quote.find(NS + 'price-details').find(NS + 'due').text | |
quotes.append((float(price), service_name)) | |
# sort by lowest price | |
quotes = sorted(quotes, key=operator.itemgetter(0)) | |
return quotes | |
def get_rate_quotes(weight, length, width, height, origin): | |
origin = origin.upper() | |
for ship_type in CODES.keys(): | |
print("*"*20, ship_type, "*"*20) | |
for place, code in CODES[ship_type][1].items(): | |
print("*** Prices for", place) | |
rates = get_rate_quote(weight, length, width, height, origin, ship_type, code) | |
for price, name in rates: | |
print("$%.2f (%s)" % (price, name)) | |
def main(): | |
parser = argparse.ArgumentParser(description='Get postal rate quotes from Canada Post') | |
parser.add_argument('weight', metavar='WEIGHT', type=float, | |
help='weight of package in kg') | |
parser.add_argument('length', metavar='LENGTH', type=float, | |
help='length of package in cm') | |
parser.add_argument('width', metavar='WIDTH', type=float, | |
help='width of package in cm') | |
parser.add_argument('height', metavar='HEIGHT', type=float, | |
help='height of package in cm') | |
parser.add_argument('origin', metavar='POSTAL_CODE', | |
help='origin postal code') | |
args = parser.parse_args() | |
get_rate_quotes(args.weight, args.length, args.width, args.height, args.origin) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
username = 'XXXX' | |
password = 'XXXX' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment