Skip to content

Commit c20d181

Browse files
committed
Use TypedDicts to better describe return values
1 parent 600bc85 commit c20d181

File tree

4 files changed

+886
-614
lines changed

4 files changed

+886
-614
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "postcode-eu-api-client"
3-
version = "1.0.1"
3+
version = "1.0.2"
44
description = "Python client for the Postcode.eu API"
55
license = "BSD-2-Clause"
66
readme = "README.md"

src/postcode_eu_api_client/client.py

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44
import sys
55
import importlib.metadata
6-
from typing import Any, cast
6+
from typing import Any
77
from urllib.parse import quote
88

99
try:
@@ -25,12 +25,23 @@
2525
UnexpectedException,
2626
)
2727

28+
from .types import (
29+
DutchAddressResult,
30+
AutocompleteResult,
31+
AutocompleteAddressResult,
32+
SupportedCountry,
33+
DutchAddressDistanceResult,
34+
DutchPostcodeRange,
35+
ValidationResult,
36+
ClientAccount,
37+
AccountInfo,
38+
)
2839

2940
class Client:
30-
SESSION_HEADER_KEY = 'X-Autocomplete-Session'
31-
SESSION_HEADER_VALUE_VALIDATION = r'^[a-z\d\-_.]{8,64}$'
41+
SESSION_HEADER_KEY: str = 'X-Autocomplete-Session'
42+
SESSION_HEADER_VALUE_VALIDATION: str = r'^[a-z\d\-_.]{8,64}$'
3243

33-
_SERVER_URL = 'https://api.postcode.eu/'
44+
_SERVER_URL: str = 'https://api.postcode.eu/'
3445

3546
def __init__(self, key: str, secret: str, platform: str):
3647
"""
@@ -41,13 +52,13 @@ def __init__(self, key: str, secret: str, platform: str):
4152
secret: The Postcode.eu API secret, provided when registering an account.
4253
platform: A platform identifier, short description of the platform using the API client.
4354
"""
44-
self._key = key
45-
self._secret = secret
46-
self._platform = platform
55+
self._key: str = key
56+
self._secret: str = secret
57+
self._platform: str = platform
4758
self._most_recent_response_headers: dict[str, list[str]] = {}
4859

4960
# Create a persistent session for connection reuse
50-
self._session = requests.Session()
61+
self._session: requests.Session = requests.Session()
5162
self._session.auth = (self._key, self._secret)
5263
self._session.headers.update({
5364
'User-Agent': self._get_user_agent()
@@ -60,7 +71,7 @@ def international_autocomplete(
6071
session: str,
6172
language: str | None = None,
6273
building_list_mode: str | None = None
63-
) -> dict[str, Any]:
74+
) -> AutocompleteResult:
6475
"""
6576
Autocomplete international addresses.
6677
@@ -80,7 +91,7 @@ def international_autocomplete(
8091

8192
return self._api_get(path, session)
8293

83-
def international_get_details(self, context: str, session: str) -> dict[str, Any]:
94+
def international_get_details(self, context: str, session: str) -> AutocompleteAddressResult:
8495
"""
8596
Get address information based on the provided autocomplete context.
8697
@@ -91,21 +102,20 @@ def international_get_details(self, context: str, session: str) -> dict[str, Any
91102

92103
return self._api_get(path, session)
93104

94-
def international_get_supported_countries(self) -> list[dict[str, str]]:
105+
def international_get_supported_countries(self) -> list[SupportedCountry]:
95106
"""
96107
Fetches list of supported countries. Recommended to cache the result.
97108
98109
https://developer.postcode.eu/documentation/international/v1/Autocomplete/getSupportedCountries
99110
"""
100-
response = self._api_get('international/v1/supported-countries', None)
101-
return cast(list[dict[str, str]], response)
111+
return self._api_get('international/v1/supported-countries', None)
102112

103113
def dutch_address_by_postcode(
104114
self,
105115
postcode: str,
106116
house_number: int,
107117
house_number_addition: str | None = None
108-
) -> dict[str, Any]:
118+
) -> DutchAddressResult:
109119
"""
110120
Get an address based on its unique combination of postcode, house number and house number addition.
111121
@@ -128,7 +138,7 @@ def dutch_address_by_postcode(
128138

129139
return self._api_get('/'.join(url_parts), None)
130140

131-
def dutch_address_rd(self, rd_x: float, rd_y: float) -> dict[str, Any]:
141+
def dutch_address_rd(self, rd_x: float, rd_y: float) -> DutchAddressDistanceResult:
132142
"""
133143
Get the closest Dutch address based on Dutch Rijksdriehoeksmeting coordinates.
134144
@@ -142,7 +152,7 @@ def dutch_address_rd(self, rd_x: float, rd_y: float) -> dict[str, Any]:
142152

143153
return self._api_get('/'.join(url_parts), None)
144154

145-
def dutch_address_lat_lon(self, latitude: float, longitude: float) -> dict[str, Any]:
155+
def dutch_address_lat_lon(self, latitude: float, longitude: float) -> DutchAddressDistanceResult:
146156
"""
147157
Get the closest Dutch address based on latitude and longitude.
148158
@@ -156,7 +166,7 @@ def dutch_address_lat_lon(self, latitude: float, longitude: float) -> dict[str,
156166

157167
return self._api_get('/'.join(url_parts), None)
158168

159-
def dutch_address_bag_number_designation(self, bag_number_designation_id: str) -> dict[str, Any]:
169+
def dutch_address_bag_number_designation(self, bag_number_designation_id: str) -> DutchAddressResult:
160170
"""
161171
Get the unique Dutch address connected to a BAG Number Designation ID ("Nummeraanduiding ID").
162172
@@ -169,7 +179,7 @@ def dutch_address_bag_number_designation(self, bag_number_designation_id: str) -
169179

170180
return self._api_get('/'.join(url_parts), None)
171181

172-
def dutch_address_bag_addressable_object(self, bag_addressable_object_id: str) -> dict[str, Any]:
182+
def dutch_address_bag_addressable_object(self, bag_addressable_object_id: str) -> list[DutchAddressResult]:
173183
"""
174184
Get the Dutch address(es) connected to a BAG Addressable Object ID.
175185
@@ -182,7 +192,7 @@ def dutch_address_bag_addressable_object(self, bag_addressable_object_id: str) -
182192

183193
return self._api_get('/'.join(url_parts), None)
184194

185-
def dutch_address_postcode_ranges(self, postcode: str) -> dict[str, Any]:
195+
def dutch_address_postcode_ranges(self, postcode: str) -> list[DutchPostcodeRange]:
186196
"""
187197
Get all streets and house number ranges for the provided postcode.
188198
@@ -211,7 +221,7 @@ def validate(
211221
building: str | None = None,
212222
region: str | None = None,
213223
street_and_building: str | None = None
214-
) -> dict[str, Any]:
224+
) -> ValidationResult:
215225
"""
216226
Validate a full address, correcting and completing all parts of the address.
217227
@@ -231,7 +241,7 @@ def validate(
231241
'streetAndBuilding': street_and_building,
232242
}
233243

234-
parameters = []
244+
parameters: list[str] = []
235245
for key, value in variables.items():
236246
if value is not None:
237247
parameters.append(f'{key}={quote(value, safe="")}')
@@ -242,7 +252,7 @@ def validate(
242252

243253
return self._api_get(path, None)
244254

245-
def get_country(self, country: str) -> dict[str, Any]:
255+
def get_country(self, country: str) -> SupportedCountry:
246256
"""
247257
Get country information.
248258
@@ -273,7 +283,7 @@ def create_client_account(
273283
invoice_address_country_iso: str,
274284
invoice_contact_name: str | None = None,
275285
is_test: bool = False
276-
) -> dict[str, Any]:
286+
) -> ClientAccount:
277287
"""
278288
Create an account for your client. The new account will be linked to your reseller account.
279289
@@ -308,7 +318,7 @@ def create_client_account(
308318

309319
return self._api_post('reseller/v1/client', post_data)
310320

311-
def account_info(self) -> dict[str, Any]:
321+
def account_info(self) -> AccountInfo:
312322
"""
313323
Retrieve basic account information for the currently authenticated account.
314324
@@ -337,19 +347,19 @@ def _validate_session_header(self, session: str) -> None:
337347
if not re.match(self.SESSION_HEADER_VALUE_VALIDATION, session, re.IGNORECASE):
338348
raise InvalidSessionValueException(
339349
f'Session value `{session}` does not conform to `{self.SESSION_HEADER_VALUE_VALIDATION}`, '
340-
'please refer to the API documentation for further information.'
350+
+ 'please refer to the API documentation for further information.'
341351
)
342352

343-
def _api_get(self, path: str, session: str | None) -> dict[str, Any]:
353+
def _api_get(self, path: str, session: str | None) -> Any:
344354
"""Perform a GET call to the API."""
345355
url = self._SERVER_URL + path
346-
headers = {}
356+
headers: dict[str, str] = {}
347357
if session is not None:
348358
headers[self.SESSION_HEADER_KEY] = session
349359

350360
return self._request('GET', url, headers=headers)
351361

352-
def _api_post(self, path: str, post_data: dict[str, Any]) -> dict[str, Any]:
362+
def _api_post(self, path: str, post_data: dict[str, Any]) -> Any:
353363
"""Perform a POST call to the API."""
354364
url = self._SERVER_URL + path
355365
return self._request('POST', url, data=post_data)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from typing import TypedDict, Literal, Any, TypeAlias
2+
3+
class DutchAddressResult(TypedDict):
4+
street: str
5+
streetNen: str
6+
houseNumber: int
7+
houseNumberAddition: str | None
8+
postcode: str
9+
city: str
10+
cityShort: str
11+
cityId: str
12+
municipality: str
13+
municipalityShort: str
14+
municipalityId: str
15+
province: str
16+
rdX: int | None
17+
rdY: int | None
18+
latitude: float | None
19+
longitude: float | None
20+
bagNumberDesignationId: str | None
21+
bagAddressableObjectId: str | None
22+
addressType: str
23+
purposes: list[str] | None
24+
surfaceArea: int | None
25+
houseNumberAdditions: list[str]
26+
27+
class AutocompleteMatch(TypedDict):
28+
value: str
29+
label: str
30+
description: str
31+
precision: str
32+
context: str
33+
highlights: list[list[int]]
34+
35+
class AutocompleteResult(TypedDict):
36+
newContext: str | None
37+
matches: list[AutocompleteMatch]
38+
39+
class AutocompleteAddress(TypedDict):
40+
country: str
41+
locality: str
42+
street: str
43+
postcode: str
44+
building: str
45+
buildingNumber: int | None
46+
buildingNumberAddition: str | None
47+
48+
class AddressLocation(TypedDict):
49+
latitude: float
50+
longitude: float
51+
precision: Literal['Address', 'Street', 'PostalCode', 'Locality']
52+
53+
class AddressCountry(TypedDict):
54+
name: str
55+
iso3Code: str
56+
iso2Code: str
57+
58+
AddressDetails: TypeAlias = dict[str, Any]
59+
60+
class AutocompleteAddressResult(TypedDict):
61+
language: str
62+
address: AutocompleteAddress
63+
mailLines: list[str]
64+
location: AddressLocation | None
65+
isPoBox: bool
66+
country: AddressCountry
67+
details: AddressDetails
68+
69+
class SupportedCountry(TypedDict):
70+
name: str
71+
iso2: str
72+
iso3: str
73+
74+
class DutchAddressDistanceResult(TypedDict):
75+
address: DutchAddressResult
76+
distance: float
77+
78+
class DutchPostcodeRange(TypedDict):
79+
street: str
80+
streetNen: str
81+
startHouseNumber: int
82+
endHouseNumber: int
83+
houseNumberType: str
84+
city: str
85+
cityShort: str
86+
cityId: str
87+
municipality: str
88+
municipalityShort: str
89+
municipalityId: str
90+
province: str
91+
92+
class ValidationMatchStatus(TypedDict):
93+
grade: str
94+
validationLevel: str
95+
isAmbiguous: bool
96+
97+
class ValidationMatchAddress(TypedDict):
98+
country: str
99+
locality: str | None
100+
street: str | None
101+
postcode: str | None
102+
building: str | None
103+
buildingNumber: int | None
104+
buildingNumberAddition: str | None
105+
region: str | None
106+
107+
class ValidationMatch(TypedDict):
108+
status: ValidationMatchStatus
109+
language: str
110+
address: ValidationMatchStatus
111+
mailLines: list[str]
112+
location: AddressLocation | None
113+
isPoBox: bool
114+
country: AddressCountry
115+
details: AddressDetails | None
116+
117+
class ValidationResult(TypedDict):
118+
country: AddressCountry
119+
matches: list[ValidationMatch]
120+
121+
class ClientAccount(TypedDict):
122+
accountId: int
123+
key: str
124+
secret: str
125+
126+
class AccountSubscription(TypedDict):
127+
startDate: str
128+
limit: int
129+
usage: int
130+
131+
class AccountInfo(TypedDict):
132+
name: str
133+
hasAccess: bool
134+
countries: list[str]
135+
adminEmail: str
136+
contactEmail: str
137+
subscription: AccountSubscription

0 commit comments

Comments
 (0)