geocodio-library-python 1.3.0__tar.gz → 1.5.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 (40) hide show
  1. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/CHANGELOG.md +14 -0
  2. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/PKG-INFO +102 -1
  3. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/README.md +101 -0
  4. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/pyproject.toml +1 -1
  5. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/_version.py +1 -1
  6. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/client.py +56 -7
  7. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/exceptions.py +21 -3
  8. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/models.py +161 -0
  9. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/e2e/test_api.py +56 -0
  10. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_geocode.py +213 -0
  11. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_models.py +107 -0
  12. geocodio_library_python-1.5.0/tests/unit/test_warnings.py +364 -0
  13. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/uv.lock +2 -2
  14. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/.github/workflows/ci.yml +0 -0
  15. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/.github/workflows/publish.yml +0 -0
  16. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/.github/workflows/test-act-event-ci.json +0 -0
  17. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/.github/workflows/test-act-event-publish.json +0 -0
  18. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/.gitignore +0 -0
  19. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/LICENSE +0 -0
  20. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/requirements-dev.txt +0 -0
  21. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/setup.cfg +0 -0
  22. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/smoke.py +0 -0
  23. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/smoke_lists.py +0 -0
  24. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/__init__.py +0 -0
  25. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/src/geocodio/distance.py +0 -0
  26. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/test_pypi_release.py +0 -0
  27. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/__init__.py +0 -0
  28. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/conftest.py +0 -0
  29. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/e2e/__init__.py +0 -0
  30. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/e2e/test_batch_reverse.py +0 -0
  31. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/e2e/test_distance.py +0 -0
  32. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/e2e/test_lists_api.py +0 -0
  33. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/test_workflows.py +0 -0
  34. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/__init__.py +0 -0
  35. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_client.py +0 -0
  36. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_distance.py +0 -0
  37. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_errors.py +0 -0
  38. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_exceptions.py +0 -0
  39. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_geocoding.py +0 -0
  40. {geocodio_library_python-1.3.0 → geocodio_library_python-1.5.0}/tests/unit/test_reverse.py +0 -0
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.5.0] - 2026-10-06
11
+
12
+ ### Added
13
+ - Warnings support: the API's `_warnings` response key is now parsed into a `warnings` list on `GeocodingResponse`, `GeocodingResult`, `ListResponse`, `DistanceJobResponse` and `PaginatedResponse` (empty when the API sent none). Previously the key was only reachable through `GeocodingResponse.raw`, and was dropped entirely for list and distance matrix job responses. For batch requests, `GeocodingResult.warnings` includes the warnings attached to that query and `GeocodingResponse.warnings` holds the de-duplicated set across all queries.
14
+ - `GeocodioError.warnings` (and `GeocodioErrorDetail.warnings`), exposing warnings attached to error responses.
15
+
16
+ ## [1.4.0] - 2026-08-28
17
+
18
+ ### Added
19
+ - 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.
20
+ - `GeocodingResult.match_type` and `GeocodingResult.address_lines`, both of which the API returns on every result and the model previously dropped.
21
+ - 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.
22
+ - 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).
23
+
10
24
  ## [1.3.0] - 2026-08-25
11
25
 
12
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: geocodio-library-python
3
- Version: 1.3.0
3
+ Version: 1.5.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
@@ -149,6 +149,107 @@ response = client.geocode(
149
149
  )
150
150
  ```
151
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
208
+ ```
209
+
210
+ ### Warnings
211
+
212
+ The API reports non-fatal advisories under a `_warnings` key — a misspelled
213
+ field name, an unexpected query parameter, a superseded API version, or an
214
+ append that had to be skipped. The request still succeeds, so nothing is raised
215
+ or logged; the warnings are parsed onto the response as a `warnings` list, which
216
+ is empty when the API sent none:
217
+
218
+ ```python
219
+ response = client.geocode("1109 N Highland St, Arlington VA", fields=["congress"])
220
+
221
+ for warning in response.warnings:
222
+ print(warning)
223
+ # "The field congress is not recognized. Did you mean cd?"
224
+ ```
225
+
226
+ Warnings show up in a few places, depending on what raised them:
227
+
228
+ | Where | Applies to |
229
+ | -- | -- |
230
+ | `response.warnings` | Single `geocode()` and `reverse()`. For batch requests, the de-duplicated warnings from every query |
231
+ | `response.results[i].warnings` | An individual result, e.g. an `ffiec` append skipped because the match is not street-level. For batch requests, this also includes the warnings attached to that query |
232
+ | `list_response.warnings` | `create_list()`, `get_list()` and `get_lists()` |
233
+ | `job.warnings` | `create_distance_matrix_job()`, `distance_matrix_job_status()` and `distance_matrix_jobs()` |
234
+
235
+ Warnings are also attached to error responses, where they are available on the
236
+ exception:
237
+
238
+ ```python
239
+ from geocodio.exceptions import GeocodioError
240
+
241
+ try:
242
+ response = client.geocode("1109 N Highland St", fields=["congress"])
243
+ except GeocodioError as e:
244
+ for warning in e.warnings:
245
+ print(warning) # "The field congress is not recognized. Did you mean cd?"
246
+ ```
247
+
248
+ > [!TIP]
249
+ > Warnings are worth logging during development — they are how the API tells
250
+ > you a field append was silently skipped, which otherwise looks like missing
251
+ > data.
252
+
152
253
  ### Address components
153
254
 
154
255
  For forward geocoding requests it is possible to supply [individual address components](https://www.geocod.io/docs/#single-address) instead of a full address string:
@@ -114,6 +114,107 @@ response = client.geocode(
114
114
  )
115
115
  ```
116
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
173
+ ```
174
+
175
+ ### Warnings
176
+
177
+ The API reports non-fatal advisories under a `_warnings` key — a misspelled
178
+ field name, an unexpected query parameter, a superseded API version, or an
179
+ append that had to be skipped. The request still succeeds, so nothing is raised
180
+ or logged; the warnings are parsed onto the response as a `warnings` list, which
181
+ is empty when the API sent none:
182
+
183
+ ```python
184
+ response = client.geocode("1109 N Highland St, Arlington VA", fields=["congress"])
185
+
186
+ for warning in response.warnings:
187
+ print(warning)
188
+ # "The field congress is not recognized. Did you mean cd?"
189
+ ```
190
+
191
+ Warnings show up in a few places, depending on what raised them:
192
+
193
+ | Where | Applies to |
194
+ | -- | -- |
195
+ | `response.warnings` | Single `geocode()` and `reverse()`. For batch requests, the de-duplicated warnings from every query |
196
+ | `response.results[i].warnings` | An individual result, e.g. an `ffiec` append skipped because the match is not street-level. For batch requests, this also includes the warnings attached to that query |
197
+ | `list_response.warnings` | `create_list()`, `get_list()` and `get_lists()` |
198
+ | `job.warnings` | `create_distance_matrix_job()`, `distance_matrix_job_status()` and `distance_matrix_jobs()` |
199
+
200
+ Warnings are also attached to error responses, where they are available on the
201
+ exception:
202
+
203
+ ```python
204
+ from geocodio.exceptions import GeocodioError
205
+
206
+ try:
207
+ response = client.geocode("1109 N Highland St", fields=["congress"])
208
+ except GeocodioError as e:
209
+ for warning in e.warnings:
210
+ print(warning) # "The field congress is not recognized. Did you mean cd?"
211
+ ```
212
+
213
+ > [!TIP]
214
+ > Warnings are worth logging during development — they are how the API tells
215
+ > you a field append was silently skipped, which otherwise looks like missing
216
+ > data.
217
+
117
218
  ### Address components
118
219
 
119
220
  For forward geocoding requests it is possible to supply [individual address components](https://www.geocod.io/docs/#single-address) instead of a full address string:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "geocodio-library-python"
7
- version = "1.3.0"
7
+ version = "1.5.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.3.0"
3
+ __version__ = "1.5.0"
@@ -58,6 +58,7 @@ from geocodio.models import (
58
58
  Location,
59
59
  PaginatedResponse,
60
60
  ProvincialRiding,
61
+ RateLimit,
61
62
  SchoolDistrict,
62
63
  Social,
63
64
  StateLegislativeDistrict,
@@ -65,6 +66,7 @@ from geocodio.models import (
65
66
  Timezone,
66
67
  UKLegislativeDistrict,
67
68
  ZIP4Data,
69
+ parse_warnings,
68
70
  )
69
71
 
70
72
 
@@ -117,6 +119,7 @@ class Geocodio:
117
119
  self._http = httpx.Client(
118
120
  base_url=f"https://{self.hostname}", verify=verify_ssl
119
121
  )
122
+ self.rate_limit: Optional[RateLimit] = None
120
123
 
121
124
  # ──────────────────────────────────────────────────────────────────────────
122
125
  # Public methods
@@ -226,7 +229,7 @@ class Geocodio:
226
229
  response = self._request(
227
230
  "POST" if data else "GET", endpoint, params, json=data, timeout=timeout
228
231
  )
229
- return self._parse_geocoding_response(response.json())
232
+ return self._parse_geocoding_response(response.json(), response=response)
230
233
 
231
234
  def reverse(
232
235
  self,
@@ -306,7 +309,7 @@ class Geocodio:
306
309
  response = self._request(
307
310
  "POST" if data else "GET", endpoint, params, json=data, timeout=timeout
308
311
  )
309
- return self._parse_geocoding_response(response.json())
312
+ return self._parse_geocoding_response(response.json(), response=response)
310
313
 
311
314
  # ──────────────────────────────────────────────────────────────────────────
312
315
  # Internal helpers
@@ -350,6 +353,12 @@ class Geocodio:
350
353
  logger.debug(f"Response headers: {resp.headers}")
351
354
  logger.debug(f"Response body: {resp.content}")
352
355
 
356
+ # Keep the most recent rate limit state available to callers, including
357
+ # for responses that raise below.
358
+ rate_limit = RateLimit.from_headers(resp.headers)
359
+ if rate_limit is not None:
360
+ self.rate_limit = rate_limit
361
+
353
362
  resp = self._handle_error_response(resp)
354
363
 
355
364
  return resp
@@ -362,17 +371,30 @@ class Geocodio:
362
371
  exception_mappings = self.get_status_exception_mappings()
363
372
  # dump the type and content of the exception mappings for debugging
364
373
  logger.error(f"Error response: {resp.status_code} - {resp.text}")
374
+
375
+ try:
376
+ warnings = parse_warnings(resp.json())
377
+ except ValueError:
378
+ warnings = []
379
+
365
380
  if resp.status_code in exception_mappings:
366
381
  exception_class = exception_mappings[resp.status_code]
367
- raise exception_class(resp.text)
382
+ raise exception_class(resp.text, warnings=warnings)
368
383
  else:
369
384
  raise GeocodioServerError(
370
- f"Unrecognized status code {resp.status_code}: {resp.text}"
385
+ f"Unrecognized status code {resp.status_code}: {resp.text}",
386
+ warnings=warnings,
371
387
  )
372
388
 
373
- def _parse_geocoding_response(self, response_json: dict) -> GeocodingResponse:
389
+ def _parse_geocoding_response(
390
+ self,
391
+ response_json: dict,
392
+ response: Optional[httpx.Response] = None,
393
+ ) -> GeocodingResponse:
374
394
  logger.debug(f"Raw response: {response_json}")
375
395
 
396
+ rate_limit = RateLimit.from_headers(response.headers) if response else None
397
+
376
398
  # Handle batch response format
377
399
  if (
378
400
  "results" in response_json
@@ -381,9 +403,14 @@ class Geocodio:
381
403
  and "response" in response_json["results"][0]
382
404
  ):
383
405
  results = []
406
+ batch_warnings: List[str] = parse_warnings(response_json)
384
407
  for res in response_json["results"]:
385
408
  query = res.get("query", "")
386
409
  matches = res.get("response", {}).get("results") or []
410
+ query_warnings = parse_warnings(res.get("response"))
411
+ batch_warnings.extend(
412
+ w for w in query_warnings if w not in batch_warnings
413
+ )
387
414
 
388
415
  # Unmatched query (e.g. an unparseable address): keep an entry
389
416
  # so the result list stays aligned with the submitted addresses,
@@ -398,6 +425,7 @@ class Geocodio:
398
425
  accuracy_type="",
399
426
  source="",
400
427
  query=query,
428
+ warnings=query_warnings,
401
429
  )
402
430
  )
403
431
  continue
@@ -416,9 +444,18 @@ class Geocodio:
416
444
  query=query,
417
445
  fields=self._parse_fields(top.get("fields")),
418
446
  stable_address_key=top.get("stable_address_key"),
447
+ match_type=top.get("match_type"),
448
+ address_lines=top.get("address_lines"),
449
+ raw=top,
450
+ warnings=query_warnings + parse_warnings(top),
419
451
  )
420
452
  )
421
- return GeocodingResponse(results=results)
453
+ return GeocodingResponse(
454
+ results=results,
455
+ raw=response_json,
456
+ rate_limit=rate_limit,
457
+ warnings=batch_warnings,
458
+ )
422
459
 
423
460
  # Handle single response format
424
461
  results = [
@@ -433,10 +470,19 @@ class Geocodio:
433
470
  source=res.get("source", ""),
434
471
  fields=self._parse_fields(res.get("fields")),
435
472
  stable_address_key=res.get("stable_address_key"),
473
+ match_type=res.get("match_type"),
474
+ address_lines=res.get("address_lines"),
475
+ raw=res,
476
+ warnings=parse_warnings(res),
436
477
  )
437
478
  for res in response_json.get("results", [])
438
479
  ]
439
- return GeocodingResponse(results=results)
480
+ return GeocodingResponse(
481
+ results=results,
482
+ raw=response_json,
483
+ rate_limit=rate_limit,
484
+ warnings=parse_warnings(response_json),
485
+ )
440
486
 
441
487
  # ──────────────────────────────────────────────────────────────────────────
442
488
  # List API methods
@@ -542,6 +588,7 @@ class Geocodio:
542
588
  first_page_url=pagination_info.get("first_page_url"),
543
589
  next_page_url=pagination_info.get("next_page_url"),
544
590
  prev_page_url=pagination_info.get("prev_page_url"),
591
+ warnings=parse_warnings(pagination_info),
545
592
  )
546
593
 
547
594
  def get_list(self, list_id: str) -> ListResponse:
@@ -593,6 +640,7 @@ class Geocodio:
593
640
  download_url=response_json.get("download_url"),
594
641
  expires_at=response_json.get("expires_at"),
595
642
  http_response=response,
643
+ warnings=parse_warnings(response_json),
596
644
  )
597
645
 
598
646
  @staticmethod
@@ -1341,6 +1389,7 @@ class Geocodio:
1341
1389
  first_page_url=pagination_info.get("first_page_url"),
1342
1390
  next_page_url=pagination_info.get("next_page_url"),
1343
1391
  prev_page_url=pagination_info.get("prev_page_url"),
1392
+ warnings=parse_warnings(pagination_info),
1344
1393
  )
1345
1394
 
1346
1395
  def get_distance_matrix_job_results(
@@ -5,7 +5,7 @@ Structured exception hierarchy for the Geocodio Python client.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from dataclasses import dataclass
8
+ from dataclasses import dataclass, field
9
9
  from typing import List, Optional, Union
10
10
 
11
11
  # ──────────────────────────────────────────────────────────────────────────────
@@ -22,6 +22,7 @@ class GeocodioErrorDetail:
22
22
  message: str
23
23
  code: Optional[int] = None # e.g. HTTP status or internal
24
24
  errors: Optional[List[str]] = None # field‑specific validation messages
25
+ warnings: List[str] = field(default_factory=list) # from ``_warnings``
25
26
 
26
27
 
27
28
  # ──────────────────────────────────────────────────────────────────────────────
@@ -32,13 +33,30 @@ class GeocodioErrorDetail:
32
33
  class GeocodioError(Exception):
33
34
  """Root of the library’s exception hierarchy."""
34
35
 
35
- def __init__(self, detail: Union[str, GeocodioErrorDetail]):
36
+ def __init__(
37
+ self,
38
+ detail: Union[str, GeocodioErrorDetail],
39
+ warnings: Optional[List[str]] = None,
40
+ ):
36
41
  if isinstance(detail, str):
37
- self.detail = GeocodioErrorDetail(message=detail)
42
+ self.detail = GeocodioErrorDetail(
43
+ message=detail, warnings=list(warnings or [])
44
+ )
38
45
  else:
39
46
  self.detail = detail
40
47
  super().__init__(self.detail.message)
41
48
 
49
+ @property
50
+ def warnings(self) -> List[str]:
51
+ """
52
+ Non-fatal advisories the API returned alongside the error.
53
+
54
+ The API appends a ``_warnings`` key to error responses just as it does
55
+ to successful ones, e.g. to point out a misspelled field name in a
56
+ request that failed for an unrelated reason.
57
+ """
58
+ return self.detail.warnings
59
+
42
60
  def __str__(self) -> str: # prettier default printing
43
61
  return self.detail.message
44
62