geocodio-library-python 1.0.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/CHANGELOG.md +15 -1
  2. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/PKG-INFO +79 -6
  3. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/README.md +77 -4
  4. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/pyproject.toml +1 -1
  5. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/_version.py +1 -1
  6. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/client.py +104 -24
  7. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/models.py +156 -1
  8. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_api.py +116 -0
  9. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_geocode.py +468 -0
  10. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_models.py +128 -0
  11. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/uv.lock +2 -2
  12. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/ci.yml +0 -0
  13. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/publish.yml +0 -0
  14. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/test-act-event-ci.json +0 -0
  15. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/test-act-event-publish.json +0 -0
  16. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.gitignore +0 -0
  17. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/LICENSE +0 -0
  18. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/requirements-dev.txt +0 -0
  19. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/setup.cfg +0 -0
  20. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/smoke.py +0 -0
  21. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/smoke_lists.py +0 -0
  22. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/__init__.py +0 -0
  23. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/distance.py +0 -0
  24. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/exceptions.py +0 -0
  25. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/test_pypi_release.py +0 -0
  26. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/__init__.py +0 -0
  27. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/conftest.py +0 -0
  28. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/__init__.py +0 -0
  29. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_batch_reverse.py +0 -0
  30. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_distance.py +0 -0
  31. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_lists_api.py +0 -0
  32. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/test_workflows.py +0 -0
  33. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/__init__.py +0 -0
  34. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_client.py +0 -0
  35. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_distance.py +0 -0
  36. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_errors.py +0 -0
  37. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_exceptions.py +0 -0
  38. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_geocoding.py +0 -0
  39. {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_reverse.py +0 -0
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2026-08-28
11
+
12
+ ### Added
13
+ - Public census accessors on `GeocodioFields`: `fields.census` (the requested append, most recent vintage when several are present), `fields.get_census(year)` (accepts `2023`, `"2023"` or `"census2023"`), `fields.census_years` and `fields.census_data`. The private `fields._census` dict and the dynamic `fields.census2023` attributes are unchanged.
14
+ - `GeocodingResult.match_type` and `GeocodingResult.address_lines`, both of which the API returns on every result and the model previously dropped.
15
+ - Raw response access: `GeocodingResponse.raw` / `.to_dict()` for the full untouched JSON payload, and `GeocodingResult.raw` / `.to_dict()` for a single result. Unlike `dataclasses.asdict()` these keep every key the API sent.
16
+ - Rate limit headers are parsed into a `RateLimit` model, exposed as `GeocodingResponse.rate_limit` and `Geocodio.rate_limit` (updated on every request, including ones that raise).
17
+
18
+ ## [1.3.0] - 2026-08-25
19
+
20
+ ### Added
21
+ - United Kingdom data append support. The `uk-westminster`, `uk-devolved`, and `uk-local` appends (and their `-next` variants) are now parsed into a typed `UKLegislativeDistrict` model, exposed via `fields.uk_westminster`, `fields.uk_devolved`, and `fields.uk_local`.
22
+
10
23
  ## [1.0.0] - 2026-06-05
11
24
 
12
25
  ### Changed
@@ -79,7 +92,8 @@ When ready to release:
79
92
  5. Push tags: `git push --tags`
80
93
  6. GitHub Actions will automatically publish to PyPI
81
94
 
82
- [Unreleased]: https://github.com/Geocodio/geocodio-library-python/compare/v1.0.0...HEAD
95
+ [Unreleased]: https://github.com/Geocodio/geocodio-library-python/compare/v1.3.0...HEAD
96
+ [1.3.0]: https://github.com/Geocodio/geocodio-library-python/compare/v1.2.0...v1.3.0
83
97
  [1.0.0]: https://github.com/Geocodio/geocodio-library-python/compare/v0.7.0...v1.0.0
84
98
  [0.7.0]: https://github.com/Geocodio/geocodio-library-python/compare/v0.6.0...v0.7.0
85
99
  [0.6.0]: https://github.com/Geocodio/geocodio-library-python/compare/v0.5.1...v0.6.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: geocodio-library-python
3
- Version: 1.0.0
3
+ Version: 1.4.0
4
4
  Summary: A Python client for the Geocodio API
5
5
  Project-URL: Homepage, https://www.geocod.io
6
6
  Project-URL: Documentation, https://www.geocod.io/docs/?python
@@ -100,6 +100,7 @@ To batch geocode, simply pass a list of addresses or coordinates instead of a si
100
100
  response = client.geocode([
101
101
  "1109 N Highland St, Arlington VA",
102
102
  "525 University Ave, Toronto, ON, Canada",
103
+ "10 Downing St, London, United Kingdom",
103
104
  "4410 S Highway 17 92, Casselberry FL",
104
105
  "15000 NE 24th Street, Redmond WA",
105
106
  "17015 Walnut Grove Drive, Morgan Hill CA"
@@ -116,15 +117,16 @@ response = client.reverse([
116
117
  response = client.geocode({
117
118
  "MyId1": "1109 N Highland St, Arlington VA",
118
119
  "MyId2": "525 University Ave, Toronto, ON, Canada",
119
- "MyId3": "4410 S Highway 17 92, Casselberry FL",
120
- "MyId4": "15000 NE 24th Street, Redmond WA",
121
- "MyId5": "17015 Walnut Grove Drive, Morgan Hill CA"
120
+ "MyId3": "10 Downing St, London, United Kingdom",
121
+ "MyId4": "4410 S Highway 17 92, Casselberry FL",
122
+ "MyId5": "15000 NE 24th Street, Redmond WA",
123
+ "MyId6": "17015 Walnut Grove Drive, Morgan Hill CA"
122
124
  })
123
125
  ```
124
126
 
125
127
  ### Field appends
126
128
 
127
- Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results and [much more](https://www.geocod.io/docs/#fields).
129
+ Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results, UK constituencies and wards, and [much more](https://www.geocod.io/docs/#fields).
128
130
 
129
131
  To request additional fields, simply supply them as a list:
130
132
 
@@ -138,6 +140,71 @@ response = client.geocode(
138
140
  )
139
141
 
140
142
  response = client.reverse("38.9002898,-76.9990361", fields=["census2010"])
143
+
144
+ # United Kingdom addresses support UK-specific appends such as Westminster and
145
+ # devolved parliament constituencies, and local authority wards
146
+ response = client.geocode(
147
+ "10 Downing St, London, United Kingdom",
148
+ fields=["uk-westminster", "uk-local"]
149
+ )
150
+ ```
151
+
152
+ #### Census appends
153
+
154
+ Census data is keyed by vintage. `fields.census` gives you the append you
155
+ requested, and `fields.get_census(year)` picks a specific vintage when you
156
+ requested more than one:
157
+
158
+ ```python
159
+ response = client.geocode("1109 N Highland St, Arlington VA", fields=["census2023"])
160
+ census = response.results[0].fields.census
161
+
162
+ print(census.full_fips) # "510131018012004"
163
+ print(census.census_year) # 2023
164
+
165
+ # When several vintages were requested
166
+ fields = response.results[0].fields
167
+ fields.get_census(2023) # also accepts "2023" or "census2023"
168
+ fields.census_years # [2023]
169
+ fields.census_data # {"census2023": CensusData(...)}
170
+ ```
171
+
172
+ `fields.census` returns the most recent vintage present. Accessing a vintage
173
+ directly (`fields.census2023`) continues to work.
174
+
175
+ ### Raw API responses
176
+
177
+ Every geocoding response keeps the untouched JSON payload the API returned, so
178
+ you can cache the full response and derive new columns later without paying for
179
+ another lookup:
180
+
181
+ ```python
182
+ response = client.geocode("1109 N Highland St, Arlington VA")
183
+
184
+ response.raw # the full JSON payload, exactly as returned
185
+ response.to_dict() # a deep copy of the same payload
186
+
187
+ result = response.results[0]
188
+ result.raw # the JSON object for this result
189
+ result.match_type # "rooftop", "unit", "building_centroid" or None
190
+ result.address_lines # ["1109 N Highland St", "", "Arlington, VA 22201"]
191
+ ```
192
+
193
+ ### Rate limits
194
+
195
+ The `X-RateLimit-*` response headers are parsed onto every geocoding response,
196
+ and the most recent values are kept on the client (including for requests that
197
+ raise):
198
+
199
+ ```python
200
+ response = client.geocode("1109 N Highland St, Arlington VA")
201
+
202
+ response.rate_limit.limit # 1000
203
+ response.rate_limit.remaining # 999
204
+ response.rate_limit.period # 60 (seconds), when sent by the API
205
+ response.rate_limit.reset # unix timestamp, when sent by the API
206
+
207
+ client.rate_limit # the most recent rate limit state seen
141
208
  ```
142
209
 
143
210
  ### Address components
@@ -163,6 +230,12 @@ response = client.geocode([
163
230
  "city": "Toronto",
164
231
  "state": "ON",
165
232
  "country": "Canada"
233
+ },
234
+ {
235
+ "street": "10 Downing St",
236
+ "city": "London",
237
+ "postal_code": "SW1A 2AA",
238
+ "country": "United Kingdom"
166
239
  }
167
240
  ])
168
241
  ```
@@ -65,6 +65,7 @@ To batch geocode, simply pass a list of addresses or coordinates instead of a si
65
65
  response = client.geocode([
66
66
  "1109 N Highland St, Arlington VA",
67
67
  "525 University Ave, Toronto, ON, Canada",
68
+ "10 Downing St, London, United Kingdom",
68
69
  "4410 S Highway 17 92, Casselberry FL",
69
70
  "15000 NE 24th Street, Redmond WA",
70
71
  "17015 Walnut Grove Drive, Morgan Hill CA"
@@ -81,15 +82,16 @@ response = client.reverse([
81
82
  response = client.geocode({
82
83
  "MyId1": "1109 N Highland St, Arlington VA",
83
84
  "MyId2": "525 University Ave, Toronto, ON, Canada",
84
- "MyId3": "4410 S Highway 17 92, Casselberry FL",
85
- "MyId4": "15000 NE 24th Street, Redmond WA",
86
- "MyId5": "17015 Walnut Grove Drive, Morgan Hill CA"
85
+ "MyId3": "10 Downing St, London, United Kingdom",
86
+ "MyId4": "4410 S Highway 17 92, Casselberry FL",
87
+ "MyId5": "15000 NE 24th Street, Redmond WA",
88
+ "MyId6": "17015 Walnut Grove Drive, Morgan Hill CA"
87
89
  })
88
90
  ```
89
91
 
90
92
  ### Field appends
91
93
 
92
- Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results and [much more](https://www.geocod.io/docs/#fields).
94
+ Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results, UK constituencies and wards, and [much more](https://www.geocod.io/docs/#fields).
93
95
 
94
96
  To request additional fields, simply supply them as a list:
95
97
 
@@ -103,6 +105,71 @@ response = client.geocode(
103
105
  )
104
106
 
105
107
  response = client.reverse("38.9002898,-76.9990361", fields=["census2010"])
108
+
109
+ # United Kingdom addresses support UK-specific appends such as Westminster and
110
+ # devolved parliament constituencies, and local authority wards
111
+ response = client.geocode(
112
+ "10 Downing St, London, United Kingdom",
113
+ fields=["uk-westminster", "uk-local"]
114
+ )
115
+ ```
116
+
117
+ #### Census appends
118
+
119
+ Census data is keyed by vintage. `fields.census` gives you the append you
120
+ requested, and `fields.get_census(year)` picks a specific vintage when you
121
+ requested more than one:
122
+
123
+ ```python
124
+ response = client.geocode("1109 N Highland St, Arlington VA", fields=["census2023"])
125
+ census = response.results[0].fields.census
126
+
127
+ print(census.full_fips) # "510131018012004"
128
+ print(census.census_year) # 2023
129
+
130
+ # When several vintages were requested
131
+ fields = response.results[0].fields
132
+ fields.get_census(2023) # also accepts "2023" or "census2023"
133
+ fields.census_years # [2023]
134
+ fields.census_data # {"census2023": CensusData(...)}
135
+ ```
136
+
137
+ `fields.census` returns the most recent vintage present. Accessing a vintage
138
+ directly (`fields.census2023`) continues to work.
139
+
140
+ ### Raw API responses
141
+
142
+ Every geocoding response keeps the untouched JSON payload the API returned, so
143
+ you can cache the full response and derive new columns later without paying for
144
+ another lookup:
145
+
146
+ ```python
147
+ response = client.geocode("1109 N Highland St, Arlington VA")
148
+
149
+ response.raw # the full JSON payload, exactly as returned
150
+ response.to_dict() # a deep copy of the same payload
151
+
152
+ result = response.results[0]
153
+ result.raw # the JSON object for this result
154
+ result.match_type # "rooftop", "unit", "building_centroid" or None
155
+ result.address_lines # ["1109 N Highland St", "", "Arlington, VA 22201"]
156
+ ```
157
+
158
+ ### Rate limits
159
+
160
+ The `X-RateLimit-*` response headers are parsed onto every geocoding response,
161
+ and the most recent values are kept on the client (including for requests that
162
+ raise):
163
+
164
+ ```python
165
+ response = client.geocode("1109 N Highland St, Arlington VA")
166
+
167
+ response.rate_limit.limit # 1000
168
+ response.rate_limit.remaining # 999
169
+ response.rate_limit.period # 60 (seconds), when sent by the API
170
+ response.rate_limit.reset # unix timestamp, when sent by the API
171
+
172
+ client.rate_limit # the most recent rate limit state seen
106
173
  ```
107
174
 
108
175
  ### Address components
@@ -128,6 +195,12 @@ response = client.geocode([
128
195
  "city": "Toronto",
129
196
  "state": "ON",
130
197
  "country": "Canada"
198
+ },
199
+ {
200
+ "street": "10 Downing St",
201
+ "city": "London",
202
+ "postal_code": "SW1A 2AA",
203
+ "country": "United Kingdom"
131
204
  }
132
205
  ])
133
206
  ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "geocodio-library-python"
7
- version = "1.0.0"
7
+ version = "1.4.0"
8
8
  description = "A Python client for the Geocodio API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """Version information for geocodio package."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.4.0"
@@ -58,11 +58,13 @@ from geocodio.models import (
58
58
  Location,
59
59
  PaginatedResponse,
60
60
  ProvincialRiding,
61
+ RateLimit,
61
62
  SchoolDistrict,
62
63
  Social,
63
64
  StateLegislativeDistrict,
64
65
  StatisticsCanadaData,
65
66
  Timezone,
67
+ UKLegislativeDistrict,
66
68
  ZIP4Data,
67
69
  )
68
70
 
@@ -116,6 +118,7 @@ class Geocodio:
116
118
  self._http = httpx.Client(
117
119
  base_url=f"https://{self.hostname}", verify=verify_ssl
118
120
  )
121
+ self.rate_limit: Optional[RateLimit] = None
119
122
 
120
123
  # ──────────────────────────────────────────────────────────────────────────
121
124
  # Public methods
@@ -225,7 +228,7 @@ class Geocodio:
225
228
  response = self._request(
226
229
  "POST" if data else "GET", endpoint, params, json=data, timeout=timeout
227
230
  )
228
- return self._parse_geocoding_response(response.json())
231
+ return self._parse_geocoding_response(response.json(), response=response)
229
232
 
230
233
  def reverse(
231
234
  self,
@@ -305,7 +308,7 @@ class Geocodio:
305
308
  response = self._request(
306
309
  "POST" if data else "GET", endpoint, params, json=data, timeout=timeout
307
310
  )
308
- return self._parse_geocoding_response(response.json())
311
+ return self._parse_geocoding_response(response.json(), response=response)
309
312
 
310
313
  # ──────────────────────────────────────────────────────────────────────────
311
314
  # Internal helpers
@@ -349,6 +352,12 @@ class Geocodio:
349
352
  logger.debug(f"Response headers: {resp.headers}")
350
353
  logger.debug(f"Response body: {resp.content}")
351
354
 
355
+ # Keep the most recent rate limit state available to callers, including
356
+ # for responses that raise below.
357
+ rate_limit = RateLimit.from_headers(resp.headers)
358
+ if rate_limit is not None:
359
+ self.rate_limit = rate_limit
360
+
352
361
  resp = self._handle_error_response(resp)
353
362
 
354
363
  return resp
@@ -369,9 +378,15 @@ class Geocodio:
369
378
  f"Unrecognized status code {resp.status_code}: {resp.text}"
370
379
  )
371
380
 
372
- def _parse_geocoding_response(self, response_json: dict) -> GeocodingResponse:
381
+ def _parse_geocoding_response(
382
+ self,
383
+ response_json: dict,
384
+ response: Optional[httpx.Response] = None,
385
+ ) -> GeocodingResponse:
373
386
  logger.debug(f"Raw response: {response_json}")
374
387
 
388
+ rate_limit = RateLimit.from_headers(response.headers) if response else None
389
+
375
390
  # Handle batch response format
376
391
  if (
377
392
  "results" in response_json
@@ -379,27 +394,50 @@ class Geocodio:
379
394
  and response_json["results"]
380
395
  and "response" in response_json["results"][0]
381
396
  ):
382
- results = [
383
- GeocodingResult(
384
- address_components=AddressComponents.from_api(
385
- res["response"]["results"][0]["address_components"]
386
- ),
387
- formatted_address=res["response"]["results"][0][
388
- "formatted_address"
389
- ],
390
- location=Location(**res["response"]["results"][0]["location"]),
391
- accuracy=res["response"]["results"][0].get("accuracy", 0.0),
392
- accuracy_type=res["response"]["results"][0].get(
393
- "accuracy_type", ""
394
- ),
395
- source=res["response"]["results"][0].get("source", ""),
396
- fields=self._parse_fields(
397
- res["response"]["results"][0].get("fields")
398
- ),
397
+ results = []
398
+ for res in response_json["results"]:
399
+ query = res.get("query", "")
400
+ matches = res.get("response", {}).get("results") or []
401
+
402
+ # Unmatched query (e.g. an unparseable address): keep an entry
403
+ # so the result list stays aligned with the submitted addresses,
404
+ # but with no coordinates.
405
+ if not matches:
406
+ results.append(
407
+ GeocodingResult(
408
+ address_components=AddressComponents.from_api({}),
409
+ formatted_address="",
410
+ location=None,
411
+ accuracy=0.0,
412
+ accuracy_type="",
413
+ source="",
414
+ query=query,
415
+ )
416
+ )
417
+ continue
418
+
419
+ top = matches[0]
420
+ results.append(
421
+ GeocodingResult(
422
+ address_components=AddressComponents.from_api(
423
+ top["address_components"]
424
+ ),
425
+ formatted_address=top["formatted_address"],
426
+ location=Location(**top["location"]),
427
+ accuracy=top.get("accuracy", 0.0),
428
+ accuracy_type=top.get("accuracy_type", ""),
429
+ source=top.get("source", ""),
430
+ query=query,
431
+ fields=self._parse_fields(top.get("fields")),
432
+ stable_address_key=top.get("stable_address_key"),
433
+ match_type=top.get("match_type"),
434
+ address_lines=top.get("address_lines"),
435
+ raw=top,
436
+ )
399
437
  )
400
- for res in response_json["results"]
401
- ]
402
- return GeocodingResponse(results=results)
438
+ return GeocodingResponse(
439
+ results=results, raw=response_json, rate_limit=rate_limit
440
+ )
403
441
 
404
442
  # Handle single response format
405
443
  results = [
@@ -413,10 +451,16 @@ class Geocodio:
413
451
  accuracy_type=res.get("accuracy_type", ""),
414
452
  source=res.get("source", ""),
415
453
  fields=self._parse_fields(res.get("fields")),
454
+ stable_address_key=res.get("stable_address_key"),
455
+ match_type=res.get("match_type"),
456
+ address_lines=res.get("address_lines"),
457
+ raw=res,
416
458
  )
417
459
  for res in response_json.get("results", [])
418
460
  ]
419
- return GeocodingResponse(results=results)
461
+ return GeocodingResponse(
462
+ results=results, raw=response_json, rate_limit=rate_limit
463
+ )
420
464
 
421
465
  # ──────────────────────────────────────────────────────────────────────────
422
466
  # List API methods
@@ -452,6 +496,7 @@ class Geocodio:
452
496
  - acs, acs-demographics, acs-economics, acs-families, acs-housing, acs-social
453
497
  - riding, provriding, provriding-next (Canadian data)
454
498
  - statcan (Statistics Canada data)
499
+ - uk-westminster, uk-westminster-next, uk-devolved, uk-devolved-next, uk-local, uk-local-next (UK legislative districts)
455
500
  - zip4 (ZIP+4 data)
456
501
  - ffiec (FFIEC data, beta)
457
502
 
@@ -832,6 +877,35 @@ class Geocodio:
832
877
  else None
833
878
  )
834
879
 
880
+ # United Kingdom fields (each append returns a list of districts). The
881
+ # ``-next`` request variants fold into these same response keys.
882
+ uk_westminster = (
883
+ [
884
+ UKLegislativeDistrict.from_api(district)
885
+ for district in fields_data["uk_westminster"]
886
+ ]
887
+ if "uk_westminster" in fields_data
888
+ else None
889
+ )
890
+
891
+ uk_devolved = (
892
+ [
893
+ UKLegislativeDistrict.from_api(district)
894
+ for district in fields_data["uk_devolved"]
895
+ ]
896
+ if "uk_devolved" in fields_data
897
+ else None
898
+ )
899
+
900
+ uk_local = (
901
+ [
902
+ UKLegislativeDistrict.from_api(district)
903
+ for district in fields_data["uk_local"]
904
+ ]
905
+ if "uk_local" in fields_data
906
+ else None
907
+ )
908
+
835
909
  # Collect all known field keys that were parsed
836
910
  parsed_keys = {
837
911
  "timezone",
@@ -856,6 +930,9 @@ class Geocodio:
856
930
  "provriding",
857
931
  "provriding-next",
858
932
  "statcan",
933
+ "uk_westminster",
934
+ "uk_devolved",
935
+ "uk_local",
859
936
  }
860
937
  # Add flat census keys that were parsed (census2000, census2020, etc.)
861
938
  # All census years are now stored in _census dict for dynamic access
@@ -878,6 +955,9 @@ class Geocodio:
878
955
  provriding=provriding,
879
956
  provriding_next=provriding_next,
880
957
  statcan=statcan,
958
+ uk_westminster=uk_westminster,
959
+ uk_devolved=uk_devolved,
960
+ uk_local=uk_local,
881
961
  extras=extras,
882
962
  _census=census_data_dict, # All census years stored here
883
963
  **acs_fields, # Dynamically include all ACS metric fields