diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..ef87bfd
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,64 @@
+name: Tests
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+ schedule:
+ - cron: "0 0 * * 0" # weekly
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ # Pin to an exact 3.9 version due to https://github.com/fudge-py/fudge/issues/13
+ python-version: [3.6, 3.7, 3.8, "3.9.6"]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies and this library
+ run: pip install -e .[test]
+ - name: Run tests
+ run: nosetests
+ lint:
+ runs-on: "ubuntu-latest"
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ with:
+ python-version: "3.9.6" # Latest python that still works with fudge
+ - name: Install dependencies and this library
+ run: pip install -e .[test]
+ - name: Run MyPy
+ run: mypy pyusps
+ build_dist:
+ name: Build wheels
+ needs: [test, lint]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ - name: Build sdist and wheel
+ run: |
+ pip install build
+ python -m build
+ - uses: actions/upload-artifact@v2
+ with:
+ name: dist
+ path: dist/
+ - name: Publish wheels to PyPI
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ pip install twine
+ twine upload dist/*
+ continue-on-error: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a079595
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,31 @@
+# CHANGELOG
+
+## 1.0.0
+
+See https://github.com/thelinuxkid/pyusps/issues/10 for
+the motivation of many of these changes.
+
+### Changed
+
+- Drop support for anythin below Python 3.6
+- Main signature changes from `verify(user_id, *args)` to `verify(user_id, args)`
+ You must always supply an iterable of inputs. Single inputs are no longer supported,
+ just wrap them in a length-one list if you need. Always returns a list of
+ `dicts`/`USPSError`s.
+- Instead of `OrderedDict`s, we just return plain old `dict`s.
+- Changed returned/raised `ValueError`s from not-found addresses to be always-returned
+ `USPSError`s
+- Instead of `TypeError` or `ValueError` from parsing errors, we now consistently raise
+ `RuntimeError`s
+- Removed `api_url` from global namespace. IDK why you would have relied on this though.
+
+### Added
+
+- Supports supplying an iterable of addresses, no longer needs the __len__ method.
+- Each supplied input now only needs to have a `__getitem__()` method. Before, it
+ needed that but also a `get()` method.
+- If you supply an empty iterable as input, you get back an empty list, not an error.
+- The new `USPSError` includes the attributes `code: str` and `description: str`
+ so you can get the original error without having to do string parsing.
+- Testing on GitHub Actions!
+- Type hints
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index a5021c6..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include README.rst
-include LICENSE
diff --git a/README.rst b/README.rst
index faa4ccd..e1446aa 100644
--- a/README.rst
+++ b/README.rst
@@ -22,14 +22,16 @@ or easy_install::
Address Information API
=======================
-This API is avaiable via the pyusps.address_information.verify
-function. It takes in the user ID given to you by the USPS
-and a variable length list of addresses to verify.
+This API is avaiable via the `pyusps.address_information.verify`
+function.
Requests
--------
-Each address is a dict containing the following required keys:
+It takes in the user ID given to you by the USPS and an iterable of addresses to verify.
+You can only supply up to 5 addresses at a time due to the API's limits.
+Each address is a dict-like (e.g. supports `__getitem__()`) containing the following
+required keys:
:address: The street address
:city: The city
@@ -44,13 +46,10 @@ The following keys are optional:
:address_extended: An apartment, suite number, etc
:urbanization: For Puerto Rico addresses only
-
-
Responses
---------
-The response will either be a dict, if a single address was requested,
-or a list of dicts, if multiple addresses were requested. Each address
+The response will either be list of `dict`s or `USPSError`s. Each `dict`
will always contain the following keys:
:address: The street address
@@ -60,7 +59,7 @@ will always contain the following keys:
:zip4: The last four numbers of the zip code
-Each address can optionally contain the following keys:
+Each `dict` can optionally contain the following keys:
:firm_name: The company name, e.g., XYZ Corp.
:address_extended: An apartment, suite number, etc
@@ -70,101 +69,60 @@ Each address can optionally contain the following keys:
*firm_name, address_extended and urbanization will return the value
requested if the API does not find a match.*
-For multiple addresses, the order in which the addresses
+If the USPS can't find an address, then in the response list, instead of a `dict` you
+will receive a `USPSError`. `USPSError` is a subclass of `RuntimeError`, and has the
+additional attributes of `code` and `description` for the error.
+
+The order in which the addresses
were specified in the request is preserved in the response.
Errors
------
-A ValueError will be raised if there's a general error, e.g.,
-invalid user id, or if a single address request generates an error.
-Except for a general error, multiple addresses requests do not raise errors.
-Instead, if one of the addresses generates an error, the
-ValueError object is returned along with the rest of the results.
+- ValueError will be raised if you request more than 5 addresses.
+- RuntimeError will be raised when the API returns a response that we can't parse
+ or otherwise doesn't make sense (You shouldn't run into this).
+- A USPSError will be raised if the supplied user_id is invalid.
+Example
+-------
-Examples
---------
+Mutiple addresses, one of them isn't found so an error is returned::
+
+ >>> from pyusps import address_information
+
+ >>> addrs = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ },
+ {
+ "address": "8 Wildwood Drive",
+ "city": "Old Lyme",
+ "state": "NJ",
+ },
+ ]
+ >>> results = address_information.verify('foo_id', addrs)
+ >>> results
+ [
+ {
+ 'address': '6406 IVY LN',
+ 'city': 'GREENBELT',
+ 'returntext': 'Default address: The address you entered was found but more '
+ 'information is needed (such as an apartment, suite, or box '
+ 'number) to match to a specific address.',
+ 'state': 'MD',
+ 'zip4': '1435',
+ 'zip5': '20770'
+ },
+ USPSError('-2147219400: Invalid City.'),
+ ]
+ >>> results[1].code
+ '-2147219400'
+ >>> res[1].description
+ 'Invalid City.'
-Single address request::
-
- from pyusps import address_information
-
- addr = dict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- address_information.verify('foo_id', addr)
- dict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
-
-Mutiple addresses request::
-
- from pyusps import address_information
-
- addrs = [
- dict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ]),
- dict([
- ('address', '8 Wildwood Drive'),
- ('city', 'Old Lyme'),
- ('state', 'CT'),
- ]),
- ]
- address_information.verify('foo_id', *addrs)
- [
- dict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ]),
- dict([
- ('address', '8 WILDWOOD DR'),
- ('city', 'OLD LYME'),
- ('state', 'CT'),
- ('zip5', '06371'),
- ('zip4', '1844'),
- ]),
- ]
-
-Mutiple addresses error::
-
- from pyusps import address_information
-
- addrs = [
- dict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ]),
- dict([
- ('address', '8 Wildwood Drive'),
- ('city', 'Old Lyme'),
- ('state', 'NJ'),
- ]),
- ]
- address_information.verify('foo_id', *addrs)
- [
- dict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ]),
- ValueError('-2147219400: Invalid City. '),
- ]
Reference
---------
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9babed1
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=43.0.0", "wheel"]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/pyusps/address_information.py b/pyusps/address_information.py
index 31964c0..ac2eb16 100644
--- a/pyusps/address_information.py
+++ b/pyusps/address_information.py
@@ -1,135 +1,133 @@
-from collections import OrderedDict
+from collections.abc import Mapping
+from typing import Any, Iterable, Union
from lxml import etree
-import pyusps.urlutil
+from urllib.parse import urlencode
+from urllib.request import urlopen
+ADDRESS_MAX = 5
-api_url = 'https://production.shippingapis.com/ShippingAPI.dll'
-address_max = 5
+class USPSError(ValueError):
+ """
+ An error from the USPS API, such as a bad `user_id` or when an address is not found.
-def _find_error(root):
- if root.tag == 'Error':
- num = root.find('Number')
- desc = root.find('Description')
- return (num, desc)
+ Inherits from ValueError. Also has attributes `code: str` and `description: str`.
+ """
+ code: str
+ description: str
-def _get_error(error):
- (num, desc) = error
- return ValueError(
- '{num}: {desc}'.format(
- num=num.text,
- desc=desc.text,
- )
- )
+ def __init__(self, code: str, description: str) -> None:
+ self.code = code
+ self.description = description
+ super().__init__(f"{code}: {description}")
-def _get_address_error(address):
- error = address.find('Error')
- if error is not None:
- error = _find_error(error)
- return _get_error(error)
+ def __eq__(self, o: object) -> bool:
+ return (
+ isinstance(o, USPSError) and
+ self.code == o.code and
+ self.description == o.description
+ )
-def _parse_address(address):
- result = OrderedDict()
+Result = Union[dict, USPSError]
+
+def _get_error(node):
+ if node.tag != 'Error':
+ return None
+ code = node.find('Number').text.strip()
+ description = node.find('Description').text.strip()
+ return USPSError(code, description)
+
+def _get_address_error(address: etree._Element) -> Union[USPSError, None]:
+ error_node = address.find('Error')
+ if error_node is None:
+ return None
+ else:
+ return _get_error(error_node)
+
+def _parse_address(address: etree._Element) -> dict:
+ result = {}
+ # More user-friendly names for street
+ # attributes
+ m = {
+ "address2": "address",
+ "address1": "address_extended",
+ "firmname": "firm_name",
+ }
for child in address.iterchildren():
# elements are yielded in order
name = child.tag.lower()
- # More user-friendly names for street
- # attributes
- if name == 'address2':
- name = 'address'
- elif name == 'address1':
- name = 'address_extended'
- elif name == 'firmname':
- name = 'firm_name'
+ name = m.get(name, name)
result[name] = child.text
-
return result
-def _process_one(address):
- # Raise address error if there's only one item
- error = _get_address_error(address)
- if error is not None:
- raise error
-
- return _parse_address(address)
-
-def _process_multiple(addresses):
+def _process_multiple(addresses: "list[etree._Element]") -> "list[Result]":
results = []
- count = 0
- for address in addresses:
+ for i, address in enumerate(addresses):
# Return error object if there are
# multiple items
+ result: Result
error = _get_address_error(address)
if error is not None:
result = error
else:
- result = _parse_address(address)
- if str(count) != address.get('ID'):
+ if str(i) != address.get('ID'):
msg = ('The addresses returned are not in the same '
'order they were requested'
)
- raise IndexError(msg)
+ raise RuntimeError(msg)
+ result = _parse_address(address)
results.append(result)
- count += 1
return results
-def _parse_response(res):
+def _parse_response(res: etree._ElementTree) -> "list[Result]":
# General error, e.g., authorization
- error = _find_error(res.getroot())
+ error = _get_error(res.getroot())
if error is not None:
- raise _get_error(error)
-
- results = res.findall('Address')
- length = len(results)
- if length == 0:
- raise TypeError(
- 'Could not find any address or error information'
- )
- if length == 1:
- return _process_one(results.pop())
- return _process_multiple(results)
-
-def _get_response(xml):
- params = OrderedDict([
- ('API', 'Verify'),
- ('XML', etree.tostring(xml)),
- ])
- url = '{api_url}?{params}'.format(
- api_url=api_url,
- params=pyusps.urlutil.urlencode(params),
- )
+ raise error
- res = pyusps.urlutil.urlopen(url)
- res = etree.parse(res)
+ elements = res.findall('Address')
+ if len(elements) == 0:
+ raise RuntimeError('Could not find any address or error information')
+ return _process_multiple(elements)
+
+def _get_response(xml: etree._Element) -> etree._ElementTree:
+ params = {'API': 'Verify', 'XML': etree.tostring(xml)}
+ param_string = urlencode(params)
+ url = f'https://production.shippingapis.com/ShippingAPI.dll?{param_string}'
+ res = urlopen(url)
+ tree = etree.parse(res)
+ return tree
+
+def _convert_input(input: "Iterable[Mapping]") -> "list[Mapping]":
+ result = []
+ for i, address in enumerate(input):
+ if i >= ADDRESS_MAX:
+ # Raise here. The Verify API will not return an error. It will
+ # just return the first 5 results
+ raise ValueError(f'Only {ADDRESS_MAX} addresses are allowed per request')
+ result.append(address)
+ return result
- return res
+def _get(mapping: Mapping, key: str) -> Any:
+ """Wrapper so that mapping only has to implement __getitem__, not get()."""
+ try:
+ return mapping[key]
+ except KeyError:
+ return None
-def _create_xml(
- user_id,
- *args
- ):
+def _create_xml(user_id: str, addresses: "list[Mapping]") -> etree._Element:
root = etree.Element('AddressValidateRequest', USERID=user_id)
- if len(args) > address_max:
- # Raise here. The Verify API will not return an error. It will
- # just return the first 5 results
- raise ValueError(
- 'Only {address_max} addresses are allowed per '
- 'request'.format(
- address_max=address_max,
- )
- )
-
- for i,arg in enumerate(args):
+ for i, arg in enumerate(addresses):
address = arg['address']
city = arg['city']
- state = arg.get('state', None)
- zip_code = arg.get('zip_code', None)
- address_extended = arg.get('address_extended', None)
- firm_name = arg.get('firm_name', None)
- urbanization = arg.get('urbanization', None)
+ state = _get(arg, 'state')
+ zip_code = _get(arg, 'zip_code')
+ address_extended = _get(arg, 'address_extended')
+ firm_name = _get(arg, 'firm_name')
+ urbanization = _get(arg, 'urbanization')
address_el = etree.Element('Address', ID=str(i))
root.append(address_el)
@@ -184,9 +182,11 @@ def _create_xml(
return root
-def verify(user_id, *args):
- xml = _create_xml(user_id, *args)
- res = _get_response(xml)
- res = _parse_response(res)
-
+def verify(user_id: str, addresses: "Iterable[Mapping]") -> "list[Union[dict, USPSError]]":
+ addresses = _convert_input(addresses)
+ if len(addresses) == 0:
+ return []
+ xml_request = _create_xml(user_id, addresses)
+ xml_response = _get_response(xml_request)
+ res = _parse_response(xml_response)
return res
diff --git a/pyusps/test/test_address_information.py b/pyusps/test/test_address_information.py
index fe2b888..132c5d0 100644
--- a/pyusps/test/test_address_information.py
+++ b/pyusps/test/test_address_information.py
@@ -1,487 +1,472 @@
+# type: ignore
+
import fudge
-from collections import OrderedDict
from nose.tools import eq_ as eq
from io import StringIO
-from pyusps.address_information import verify
+from pyusps.address_information import verify, USPSError
from pyusps.test.util import assert_raises, assert_errors_equal
-@fudge.patch('pyusps.urlutil.urlopen')
+def setup_urlopen_mock(mock, expects, return_str):
+ mock = mock.expects_call().with_args(expects)
+ mock = mock.returns(StringIO(return_str))
+ return mock
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_simple(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%3E20770%3C%2FZip5%3E%3CZip4%3E%3C%2FZip4%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ('zip_code', '20770'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ 'address': '6406 Ivy Lane',
+ 'city': 'Greenbelt',
+ 'state': 'MD',
+ 'zip_code': '20770',
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_zip5(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%3E20770%3C%2FZip5%3E%3CZip4%3E%3C%2FZip4%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ('zip_code', '20770'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ "zip_code": "20770",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_zip_both(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%3E20770%3C%2FZip5%3E%3CZip4%3E1441%3C%2FZip4%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ('zip_code', '207701441'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ "zip_code": "207701441",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_zip_dash(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%3E20770%3C%2FZip5%3E%3CZip4%3E1441%3C%2FZip4%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ('zip_code', '20770-1441'),
- ])
- res = verify(
- 'foo_id',
- address
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ "zip_code": "20770-1441",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_zip_only(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%2F%3E%3CZip5%3E20770%3C%2FZip5%3E%3CZip4%3E%3C%2FZip4%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('zip_code', '20770'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "zip_code": "20770",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_state_only(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_firm_name(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CFirmName%3EXYZ+Corp%3C%2FFirmName%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-XYZ CORP6406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('firm_name', 'XYZ Corp'),
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""XYZ CORP6406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "firm_name": "XYZ Corp",
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('firm_name', 'XYZ CORP'),
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "firm_name": "XYZ CORP",
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_address_extended(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%3ESuite+12%3C%2FAddress1%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-STE 126406 IVY LNGREENBELTMD207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('address_extended', 'Suite 12'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
-
- expected = OrderedDict([
- ('address_extended', 'STE 12'),
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ res = u"""STE 126406 IVY LNGREENBELTMD207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "address_extended": "Suite 12",
+ "city": "Greenbelt",
+ "state": "MD",
+ }
+ ]
+ res = verify('foo_id', address)
+
+ expected = [{
+ "address_extended": "STE 12",
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_urbanization(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CUrbanization%3EPuerto+Rico%3C%2FUrbanization%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMDPUERTO RICO207701441""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('urbanization', 'Puerto Rico'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- res = verify(
- 'foo_id',
- address,
- )
+ res = u"""6406 IVY LNGREENBELTMDPUERTO RICO207701441"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ 'urbanization': 'Puerto Rico',
+ 'city': 'Greenbelt',
+ 'state': 'MD',
+ }
+ ]
+ res = verify('foo_id', address)
- expected = OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('urbanization', 'PUERTO RICO'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ])
+ expected = [
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "urbanization": "PUERTO RICO",
+ "zip5": "20770",
+ "zip4": "1441",
+ }
+ ]
eq(res, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_multiple(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3CAddress+ID%3D%221%22%3E%3CAddress1%2F%3E%3CAddress2%3E8+Wildwood+Drive%3C%2FAddress2%3E%3CCity%3EOld+Lyme%3C%2FCity%3E%3CState%3ECT%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD2077014418 WILDWOOD DROLD LYMECT063711844""")
- fake_urlopen.returns(res)
-
- addresses = [
- OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ]),
- OrderedDict([
- ('address', '8 Wildwood Drive'),
- ('city', 'Old Lyme'),
- ('state', 'CT'),
- ]),
- ]
- res = verify(
- 'foo_id',
- *addresses
- )
+ res = u"""6406 IVY LNGREENBELTMD2077014418 WILDWOOD DROLD LYMECT063711844"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ addresses_list = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ },
+ {
+ "address": "8 Wildwood Drive",
+ "city": "Old Lyme",
+ "state": "CT",
+ },
+ ]
+ addresses_generator = (a for a in addresses_list)
expected = [
- OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ]),
- OrderedDict([
- ('address', '8 WILDWOOD DR'),
- ('city', 'OLD LYME'),
- ('state', 'CT'),
- ('zip5', '06371'),
- ('zip4', '1844'),
- ]),
- ]
- eq(res, expected)
-
-@fudge.patch('pyusps.urlutil.urlopen')
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ },
+ {
+ "address": "8 WILDWOOD DR",
+ "city": "OLD LYME",
+ "state": "CT",
+ "zip5": "06371",
+ "zip4": "1844",
+ },
+ ]
+
+ for inp in (addresses_list, addresses_generator):
+ res = verify('foo_id', inp)
+ eq(res, expected)
+
+
+def test_empty_input():
+ """We can handle empty input."""
+ result = verify("user_id", [])
+ expected = []
+ eq(result, expected)
+
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_more_than_5(fake_urlopen):
- addresses = [
- OrderedDict(),
- OrderedDict(),
- OrderedDict(),
- OrderedDict(),
- OrderedDict(),
- OrderedDict(),
- ]
-
- msg = assert_raises(
+ addr = {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ }
+ addresses = [addr] * 6
+
+ err = assert_raises(
ValueError,
verify,
'foo_id',
- *addresses
+ addresses
)
+ expected = ValueError('Only 5 addresses are allowed per request')
+ assert_errors_equal(err, expected)
- eq(str(msg), 'Only 5 addresses are allowed per request')
-@fudge.patch('pyusps.urlutil.urlopen')
+def test_error_properties():
+ """We can treat these pretty much like a ValueError."""
+ err = USPSError("code", "description")
+ err2 = USPSError("code", "description")
+ eq(err.code, "code")
+ eq(err.description, "description")
+ eq(err.args, ("code: description", ))
+ eq(str(err), "code: description")
+ assert isinstance(err, ValueError)
+ eq(err, err2)
+
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_api_root_error(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
+ res = u"""
80040b1a
Authorization failure. Perhaps username and/or password is incorrect.
UspsCom::DoAuth
-""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ])
- msg = assert_raises(
- ValueError,
+ """
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ }
+ err = assert_raises(
+ USPSError,
verify,
'foo_id',
- address
+ [address]
)
- expected = ('80040b1a: Authorization failure. Perhaps username '
- 'and/or password is incorrect.'
- )
- eq(str(msg), expected)
+ expected = USPSError(
+ "80040b1a",
+ "Authorization failure. Perhaps username and/or password is incorrect."
+ )
+ eq(err, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_api_address_error_single(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3ENJ%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
--2147219401API_AddressCleancAddressClean.CleanAddress2;SOLServer.CallAddressDllAddress Not Found.1000440""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'NJ'),
- ])
- msg = assert_raises(
- ValueError,
- verify,
- 'foo_id',
- address
- )
-
- expected = '-2147219401: Address Not Found.'
- eq(str(msg), expected)
-
-@fudge.patch('pyusps.urlutil.urlopen')
+ res = u"""-2147219401API_AddressCleancAddressClean.CleanAddress2;SOLServer.CallAddressDllAddress Not Found.1000440"""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "NJ",
+ }
+ ]
+ res = verify('foo_id', address)
+
+ eq(len(res), 1)
+ expected = USPSError("-2147219401", "Address Not Found.")
+ eq(res[0], expected)
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_api_address_error_multiple(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3CAddress+ID%3D%221%22%3E%3CAddress1%2F%3E%3CAddress2%3E8+Wildwood+Drive%3C%2FAddress2%3E%3CCity%3EOld+Lyme%3C%2FCity%3E%3CState%3ENJ%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD207701441-2147219400API_AddressCleancAddressClean.CleanAddress2;SOLServer.CallAddressDllInvalid City.1000440""")
- fake_urlopen.returns(res)
+ res = u"""6406 IVY LNGREENBELTMD207701441-2147219400API_AddressCleancAddressClean.CleanAddress2;SOLServer.CallAddressDllInvalid City. 1000440"""
+ setup_urlopen_mock(fake_urlopen, req, res)
addresses = [
- OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ]),
- OrderedDict([
- ('address', '8 Wildwood Drive'),
- ('city', 'Old Lyme'),
- ('state', 'NJ'),
- ]),
- ]
- res = verify(
- 'foo_id',
- *addresses
- )
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "MD",
+ },
+ {
+ "address": "8 Wildwood Drive",
+ "city": "Old Lyme",
+ "state": "NJ",
+ },
+ ]
+ res = verify('foo_id', addresses)
# eq does not work with exceptions. Process each item manually.
eq(len(res), 2)
eq(
res[0],
- OrderedDict([
- ('address', '6406 IVY LN'),
- ('city', 'GREENBELT'),
- ('state', 'MD'),
- ('zip5', '20770'),
- ('zip4', '1441'),
- ]),
- )
- assert_errors_equal(
- res[1],
- ValueError('-2147219400: Invalid City.'),
- )
-
-@fudge.patch('pyusps.urlutil.urlopen')
+ {
+ "address": "6406 IVY LN",
+ "city": "GREENBELT",
+ "state": "MD",
+ "zip5": "20770",
+ "zip4": "1441",
+ },
+ )
+ expected = USPSError("-2147219400", "Invalid City.")
+ eq(res[1], expected)
+
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_api_empty_error(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3ENJ%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-""")
- fake_urlopen.returns(res)
-
- address = OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'NJ'),
- ])
- msg = assert_raises(
- TypeError,
+ res = u""""""
+ setup_urlopen_mock(fake_urlopen, req, res)
+
+ address = [
+ {
+ "address": "6406 Ivy Lane",
+ "city": "Greenbelt",
+ "state": "NJ",
+ }
+ ]
+ err = assert_raises(
+ RuntimeError,
verify,
'foo_id',
address
)
- expected = 'Could not find any address or error information'
- eq(str(msg), expected)
+ expected = RuntimeError('Could not find any address or error information')
+ assert_errors_equal(err, expected)
-@fudge.patch('pyusps.urlutil.urlopen')
+@fudge.patch('pyusps.address_information.urlopen')
def test_verify_api_order_error(fake_urlopen):
- fake_urlopen = fake_urlopen.expects_call()
req = """https://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=%3CAddressValidateRequest+USERID%3D%22foo_id%22%3E%3CAddress+ID%3D%220%22%3E%3CAddress1%2F%3E%3CAddress2%3E6406+Ivy+Lane%3C%2FAddress2%3E%3CCity%3EGreenbelt%3C%2FCity%3E%3CState%3EMD%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3CAddress+ID%3D%221%22%3E%3CAddress1%2F%3E%3CAddress2%3E8+Wildwood+Drive%3C%2FAddress2%3E%3CCity%3EOld+Lyme%3C%2FCity%3E%3CState%3ECT%3C%2FState%3E%3CZip5%2F%3E%3CZip4%2F%3E%3C%2FAddress%3E%3C%2FAddressValidateRequest%3E"""
- fake_urlopen = fake_urlopen.with_args(req)
- res = StringIO(u"""
-6406 IVY LNGREENBELTMD2077014418 WILDWOOD DROLD LYMECT063711844""")
- fake_urlopen.returns(res)
+ res = u"""6406 IVY LNGREENBELTMD2077014418 WILDWOOD DROLD LYMECT063711844"""
+ setup_urlopen_mock(fake_urlopen, req, res)
addresses = [
- OrderedDict([
- ('address', '6406 Ivy Lane'),
- ('city', 'Greenbelt'),
- ('state', 'MD'),
- ]),
- OrderedDict([
- ('address', '8 Wildwood Drive'),
- ('city', 'Old Lyme'),
- ('state', 'CT'),
- ]),
- ]
- msg = assert_raises(
- IndexError,
+ {
+ 'address': '6406 Ivy Lane',
+ 'city': 'Greenbelt',
+ 'state': 'MD',
+ },
+ {
+ 'address': '8 Wildwood Drive',
+ 'city': 'Old Lyme',
+ 'state': 'CT',
+ }
+ ]
+ err = assert_raises(
+ RuntimeError,
verify,
'foo_id',
- *addresses
+ addresses
)
-
- expected = ('The addresses returned are not in the same order '
- 'they were requested'
- )
- eq(str(msg), expected)
+ expected = RuntimeError(
+ 'The addresses returned are not in the same order they were requested'
+ )
+ assert_errors_equal(err, expected)
diff --git a/pyusps/urlutil.py b/pyusps/urlutil.py
deleted file mode 100644
index 5feae2e..0000000
--- a/pyusps/urlutil.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# `fudge.patch` in `pyusps.test.test_address_information` needs the full
-# module path as well as the function name as its argument, e.g.,
-# "urllib2.urlopen". Create a normalized module path here for
-# urllib2/urllib functions in order to support both Python 2 and Python 3.
-
-try:
- from urllib.request import urlopen as _urlopen
-except ImportError:
- from urllib2 import urlopen as _urlopen
-
-try:
- from urllib.parse import urlencode as _urlencode
-except ImportError:
- from urllib import urlencode as _urlencode
-
-urlopen = _urlopen
-urlencode = _urlencode
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..97aa907
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,38 @@
+[metadata]
+name = pyusps
+version = 0.0.7
+author = Andres Buritica
+author_email = andres@thelinuxkid.com
+description = pyusps -- Python bindings for the USPS Ecommerce APIs
+license = MIT
+license_file = LICENSE
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+url = https://github.com/thelinuxkid/pyusps
+project_urls =
+ Tracker = https://github.com/thelinuxkid/pyusps/issues
+ Source = https://github.com/thelinuxkid/pyusps
+classifiers =
+ Development Status :: 4 - Beta
+ Intended Audience :: Developers
+ Natural Language :: English
+ License :: OSI Approved :: MIT License
+ Programming Language :: Python
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Programming Language :: Python :: 3.9
+
+[options]
+python_requires = >=3.6
+install_requires =
+ lxml>=2.3.3
+
+[options.extras_require]
+test =
+ fudge>=1.1.1
+ lxml_stubs>=0.4.0
+ mypy>=0.942
+ nose>=1.3.7
+dev =
+ ipython>=5.5.0
\ No newline at end of file
diff --git a/setup.py b/setup.py
index e7fbdc5..f13cf21 100755
--- a/setup.py
+++ b/setup.py
@@ -1,49 +1,3 @@
-#!/usr/bin/python
-from setuptools import setup, find_packages
-import os
-
-EXTRAS_REQUIRES = dict(
- test=[
- 'fudge>=1.1.1',
- 'nose>=1.3.7',
- ],
- dev=[
- 'ipython>=5.5.0',
- ],
- )
-
-# Pypi package documentation
-root = os.path.dirname(__file__)
-path = os.path.join(root, 'README.rst')
-with open(path) as fp:
- long_description = fp.read()
-
-setup(
- name='pyusps',
- version='0.0.7',
- description='pyusps -- Python bindings for the USPS Ecommerce APIs',
- long_description=long_description,
- author='Andres Buritica',
- author_email='andres@thelinuxkid.com',
- maintainer='Andres Buritica',
- maintainer_email='andres@thelinuxkid.com',
- url='https://github.com/thelinuxkid/pyusps',
- license='MIT',
- packages = find_packages(),
- namespace_packages = ['pyusps'],
- test_suite='nose.collector',
- install_requires=[
- 'setuptools>=0.6c11',
- 'lxml>=2.3.3',
- ],
- extras_require=EXTRAS_REQUIRES,
- classifiers=[
- 'Development Status :: 4 - Beta',
- 'Intended Audience :: Developers',
- 'Natural Language :: English',
- 'License :: OSI Approved :: MIT License',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3.5'
- ],
-)
+from setuptools import setup
+if __name__ == '__main__':
+ setup()