Skip to content
This repository was archived by the owner on Mar 22, 2022. It is now read-only.

Commit 832507d

Browse files
authored
Version 1.1.0
* Improved tests and modified data dictionary * Added README.rst and updated Pipfile * Updated setup.py to use README.rst
1 parent b5982be commit 832507d

33 files changed

+611
-698
lines changed

.editorconfig

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
# EditorConfig helps developers define and maintain consistent
2-
# coding styles between different editors and IDEs
3-
# editorconfig.org
4-
51
[*]
62
end_of_line = lf
73
charset = utf-8
84
trim_trailing_whitespace = true
9-
insert_final_newline = true
5+
insert_final_newline = true

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ dist/
1313

1414
# Unit test / coverage reports
1515
htmlcov/
16-
.coverage
16+
.coverage
17+
18+
# IDE
19+
.vscode

.travis.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@ python:
33
- "3.4"
44
- "3.5"
55
- "3.6"
6+
# Enable Python 3.7
7+
matrix:
8+
include:
9+
- python: 3.7
10+
dist: xenial
11+
sudo: true
612
install:
7-
- 'pip install pipenv'
8-
- 'pipenv sync --dev'
13+
- 'pip install pytest'
14+
- 'pip install pytest-cov'
915
- 'pip install coveralls'
16+
- 'pip install requests-mock'
1017
script:
11-
- 'pipenv run pytest'
18+
- 'pytest'
1219
after_success:
1320
- 'coveralls'

Pipfile

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ name = "pypi"
77
requests = "*"
88

99
[dev-packages]
10-
mock = "*"
11-
pytest = "==4.0.0"
12-
pytest-mock = "==1.10.0"
13-
pytest-cov = "==2.6.0"
10+
pytest = "*"
11+
pytest-cov = "*"
1412
"pathlib2" = "*"
1513
scandir = "*"
14+
black = "*"
15+
requests-mock = "*"
16+
17+
[pipenv]
18+
allow_prereleases = true

Pipfile.lock

Lines changed: 135 additions & 84 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
### Usage
1717

18-
Here is an example of how to use the API client. First you initialize a client with your API key, after that you use
19-
one of the four getter methods to fetch the data you need. They all return the actual JSON response converted to a
18+
Here is an example of how to use the API client. First you initialize a client with your API key, after that you use
19+
one of the four getter methods to fetch the data you need. They all return the actual JSON response converted to a
2020
Python dictionary.
2121

2222
```python
@@ -30,15 +30,15 @@ client = PostcodeAPIClient(api_key="YOUR_API_KEY")
3030
# The postal_code and number parameters are optional
3131
# The number parameter only works together with postal_code
3232
data = client.get_all_addresses(postal_code="5038EA", number=19)
33-
addresses = data["_embedded"]["addresses"] # List of addresses
33+
addresses = data["results"] # List of addresses
3434

3535
# Fetch a single address
3636
address = client.get_address(address_id="0855200000046355")
3737

38-
# Fetch a list of postal codes (within a specific area)
38+
# Fetch a list of postal codes (within a specific area)
3939
# The area parameter is optional
4040
data = client.get_all_postal_codes(area="5038")
41-
postal_codes = data['_embedded']['postcodes'] # List of postal codes
41+
postal_codes = data["results"] # List of postal codes
4242

4343
# Fetch a single postal code
4444
postal_code = client.get_postal_code("5038EA")
@@ -51,5 +51,5 @@ For more information about the data that is returned, please refer to the [offic
5151
To run the tests, make sure you have the dev dependencies installed, and run `pytest` in the root of the project.
5252

5353
## Issues
54-
If you have any issues with the API wrapper, please post them [here](https://github.com/infoklik/postcodeapi/issues). If you have issues with the actual API,
54+
If you have any issues with the API wrapper, please post them [here](https://github.com/infoklik/postcodeapi/issues). If you have issues with the actual API,
5555
please post them in the [official issue tracker](https://github.com/postcodeapi/postcodeapi/issues) of Postcode API.

README.rst

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
postcodeapi
2+
===========
3+
4+
``postcodeapi`` is a tiny Python wrapper around the Postcode API V2.
5+
6+
|PyPI version| |Build Status| |Requirements Status| |Coverage Status|
7+
8+
Installation and usage
9+
----------------------
10+
11+
Installation
12+
~~~~~~~~~~~~
13+
14+
*postcodeapi* can be installed by running ``pip install postcodeapi``.
15+
16+
Usage
17+
~~~~~
18+
19+
Here is an example of how to use the API client. First you initialize a
20+
client with your API key, after that you use one of the four getter
21+
methods to fetch the data you need. They all return the actual JSON
22+
response converted to a Python dictionary.
23+
24+
.. code:: python
25+
26+
# Import the PostcodeAPIClient
27+
from postcodeapi.client import PostcodeAPIClient
28+
29+
# Initialize a client with your API key
30+
client = PostcodeAPIClient(api_key="YOUR_API_KEY")
31+
32+
# Fetch a list of addresses (for a given postal_code and number)
33+
# The postal_code and number parameters are optional
34+
# The number parameter only works together with postal_code
35+
data = client.get_all_addresses(postal_code="5038EA", number=19)
36+
addresses = data["results"] # List of addresses
37+
38+
# Fetch a single address
39+
address = client.get_address(address_id="0855200000046355")
40+
41+
# Fetch a list of postal codes (within a specific area)
42+
# The area parameter is optional
43+
data = client.get_all_postal_codes(area="5038")
44+
postal_codes = data["results"] # List of postal codes
45+
46+
# Fetch a single postal code
47+
postal_code = client.get_postal_code("5038EA")
48+
49+
Documentation
50+
-------------
51+
52+
For more information about the data that is returned, please refer to
53+
the `official API documentation`_. It is written in Dutch.
54+
55+
Running tests
56+
-------------
57+
58+
To run the tests, make sure you have the dev dependencies installed, and
59+
run ``pytest`` in the root of the project.
60+
61+
Issues
62+
------
63+
64+
If you have any issues with the API wrapper, please post them `here`_.
65+
If you have issues with the actual API, please post them in the
66+
`official issue tracker`_ of Postcode API.
67+
68+
.. _official API documentation: https://www.postcodeapi.nu/docs/
69+
.. _here: https://github.com/infoklik/postcodeapi/issues
70+
.. _official issue tracker: https://github.com/postcodeapi/postcodeapi/issues
71+
72+
.. |PyPI version| image:: https://badge.fury.io/py/postcodeapi.svg
73+
:target: https://badge.fury.io/py/postcodeapi
74+
.. |Build Status| image:: https://travis-ci.org/roedesh/postcodeapi.svg?branch=master
75+
:target: https://travis-ci.org/roedesh/postcodeapi
76+
.. |Requirements Status| image:: https://requires.io/github/roedesh/postcodeapi/requirements.svg?branch=master
77+
:target: https://requires.io/github/roedesh/postcodeapi/requirements/?branch=master
78+
.. |Coverage Status| image:: https://coveralls.io/repos/github/roedesh/postcodeapi/badge.svg?branch=master
79+
:target: https://coveralls.io/github/roedesh/postcodeapi?branch=master

postcodeapi/client.py

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import json
2-
from urllib.parse import urlencode
2+
from urllib.parse import parse_qs, urlencode, urlparse
33

44
import requests
5-
65
from postcodeapi import exceptions
76
from postcodeapi.utils import is_valid_postal_code
87

@@ -13,25 +12,56 @@
1312
POSTCODE_API_POSTAL_CODE = "postcodes/{}"
1413

1514

15+
def generate_list_result(data, identifier="addresses"):
16+
"""Generate a dictionary with the results and the next id or
17+
postal code if it's available.
18+
19+
20+
Arguments:
21+
data {dict} -- Data dictionary
22+
23+
Keyword Arguments:
24+
identifier {str} -- Dictionary key which holds the results (default: {"addresses"})
25+
26+
Returns:
27+
dict -- Dictionary with the results and the next id or
28+
postal code
29+
"""
30+
31+
results = data.get("_embedded", {}).get(identifier, {})
32+
next_url = data.get("_links", {}).get("next", {}).get("href", "")
33+
34+
# Extract the id or postal code from the "next" url querystring
35+
if next_url:
36+
parsed_qs = parse_qs(urlparse(next_url).query)
37+
if identifier == "addresses":
38+
next = parsed_qs.get("from[id]", None)
39+
else:
40+
next = parsed_qs.get("from[postcode]", None)
41+
else:
42+
next = None
43+
44+
return {"results": results, "next": next}
45+
46+
1647
class PostcodeAPIClient:
1748
"""
1849
A wrapper around the Postcode API V2.
19-
It allows to filter trough addresses and postal codes.
50+
It allows for filtering trough addresses and postal codes.
2051
21-
All getter methods return JSON data as a Python object.
52+
All getter methods return JSON data as a Python dictionary.
2253
2354
Full documentation can be found on https://www.postcodeapi.nu/docs/
2455
"""
2556

26-
def __init__(self, api_key, compact=False):
57+
def __init__(self, api_key):
2758
"""
2859
Initializes a new API client for the Postcode API v2. Requires an API key.
2960
Upon initialization the required headers will be pre-set for later use.
3061
3162
:param api_key: Postcode API V2 key
3263
"""
3364
self._headers = {"x-api-key": api_key, "accept": "application/hal+json"}
34-
self.compact = compact
3565

3666
def _do_request(self, endpoint, querystring=None):
3767
"""
@@ -53,22 +83,33 @@ def _do_request(self, endpoint, querystring=None):
5383
raise exceptions.ResourceNotFound
5484
return json.loads(data)
5585

56-
def get_all_addresses(self, postal_code=None, number=0, from_id=""):
86+
def get_all_addresses(self, postal_code=None, number=0, from_id=None):
5787
"""
58-
Return a list of addresses. Can be filtered by providing a postal code and/or house number.
88+
Return a list of addresses. Can be filtered by providing a postal code and house number.
5989
Filtering on a house number requires a postal code
6090
61-
:param postal_code: Postal code to filter on
91+
:param postal_code: (Optional) Postal code to filter on
6292
:param number: (Optional) House number to filter on
6393
:param from_id: (Optional) The address ID to start from
6494
:return: List of address dictionaries
6595
"""
66-
if number and not postal_code:
67-
raise exceptions.HouseNumberRequiresPostalCode
68-
if postal_code and not is_valid_postal_code(postal_code):
69-
raise exceptions.InvalidPostalCode
70-
querystring = {"postcode": postal_code, "number": number, "from[id]": from_id}
71-
return self._do_request(POSTCODE_API_ALL_ADDRESSES, querystring)
96+
querystring = {}
97+
98+
if number:
99+
if not postal_code:
100+
raise exceptions.HouseNumberRequiresPostalCode
101+
querystring.update({"number": number})
102+
103+
if postal_code:
104+
if not is_valid_postal_code(postal_code):
105+
raise exceptions.InvalidPostalCode
106+
querystring.update({"postcode": postal_code})
107+
108+
if from_id:
109+
querystring.update({"from[id]": from_id})
110+
111+
data = self._do_request(POSTCODE_API_ALL_ADDRESSES, querystring)
112+
return generate_list_result(data, "addresses")
72113

73114
def get_address(self, address_id):
74115
"""
@@ -77,10 +118,9 @@ def get_address(self, address_id):
77118
:param address_id: ID of the address
78119
:return: Single address based on ID
79120
"""
80-
data = self._do_request(POSTCODE_API_ADDRESS.format(address_id))
81-
return data
121+
return self._do_request(POSTCODE_API_ADDRESS.format(address_id))
82122

83-
def get_all_postal_codes(self, area=None, from_postal_code=""):
123+
def get_all_postal_codes(self, area=None, from_postal_code=None):
84124
"""
85125
Return a list of postal codes. Can be filtered based on a given postal area.
86126
@@ -90,14 +130,22 @@ def get_all_postal_codes(self, area=None, from_postal_code=""):
90130
"""
91131
if from_postal_code and not is_valid_postal_code(from_postal_code):
92132
raise exceptions.InvalidPostalCode
93-
querystring = {
94-
"postcodeArea": area,
95-
"from[postcode]": from_postal_code,
96-
# We need to provide both from[postcode] and from[postcodeArea] in order for pagination to work
97-
# So we use the four numbers from the postal code as the area code
98-
"from[postcodeArea]": from_postal_code[:4],
99-
}
100-
return self._do_request(POSTCODE_API_ALL_POSTAL_CODES, querystring)
133+
134+
querystring = {}
135+
if area:
136+
querystring.update({"postcodeArea": area})
137+
138+
if from_postal_code:
139+
querystring.update(
140+
{
141+
"from[postcode]": from_postal_code,
142+
# We need to provide both from[postcode] and from[postcodeArea] in order for pagination to work
143+
# So we use the four numbers from the postal code as the area code
144+
"from[postcodeArea]": from_postal_code[:4],
145+
}
146+
)
147+
data = self._do_request(POSTCODE_API_ALL_POSTAL_CODES, querystring)
148+
return generate_list_result(data, "postcodes")
101149

102150
def get_postal_code(self, postal_code):
103151
"""

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
setup(
44
name="postcodeapi",
5-
version="1.0.2",
5+
version="1.1.0",
66
description="A tiny wrapper around the Postcode API v2",
7-
long_description=open("README.md").read(),
7+
long_description=open("README.rst").read(),
88
url="http://github.com/roedesh/postcodeapi",
99
author="Ruud Schroën",
1010
author_email="schroenruud@gmail.com",

0 commit comments

Comments
 (0)