geocodio-library-python 1.0.0__tar.gz → 1.3.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.
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/CHANGELOG.md +7 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/PKG-INFO +21 -6
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/README.md +19 -4
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/pyproject.toml +1 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/_version.py +1 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/client.py +75 -19
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/models.py +27 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/test_api.py +60 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_geocode.py +255 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_models.py +21 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/uv.lock +2 -2
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.github/workflows/ci.yml +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.github/workflows/publish.yml +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.github/workflows/test-act-event-ci.json +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.github/workflows/test-act-event-publish.json +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.gitignore +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/LICENSE +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/requirements-dev.txt +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/setup.cfg +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/smoke.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/smoke_lists.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/src/geocodio/exceptions.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/test_pypi_release.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/conftest.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/test_batch_reverse.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/test_distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/test_lists_api.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/test_workflows.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_client.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_errors.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_exceptions.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_geocoding.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_reverse.py +0 -0
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.0] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- 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`.
|
|
14
|
+
|
|
10
15
|
## [1.0.0] - 2026-06-05
|
|
11
16
|
|
|
12
17
|
### Changed
|
|
@@ -79,7 +84,8 @@ When ready to release:
|
|
|
79
84
|
5. Push tags: `git push --tags`
|
|
80
85
|
6. GitHub Actions will automatically publish to PyPI
|
|
81
86
|
|
|
82
|
-
[Unreleased]: https://github.com/Geocodio/geocodio-library-python/compare/v1.
|
|
87
|
+
[Unreleased]: https://github.com/Geocodio/geocodio-library-python/compare/v1.3.0...HEAD
|
|
88
|
+
[1.3.0]: https://github.com/Geocodio/geocodio-library-python/compare/v1.2.0...v1.3.0
|
|
83
89
|
[1.0.0]: https://github.com/Geocodio/geocodio-library-python/compare/v0.7.0...v1.0.0
|
|
84
90
|
[0.7.0]: https://github.com/Geocodio/geocodio-library-python/compare/v0.6.0...v0.7.0
|
|
85
91
|
[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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: geocodio-library-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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": "
|
|
120
|
-
"MyId4": "
|
|
121
|
-
"MyId5": "
|
|
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,13 @@ 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
|
+
)
|
|
141
150
|
```
|
|
142
151
|
|
|
143
152
|
### Address components
|
|
@@ -163,6 +172,12 @@ response = client.geocode([
|
|
|
163
172
|
"city": "Toronto",
|
|
164
173
|
"state": "ON",
|
|
165
174
|
"country": "Canada"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"street": "10 Downing St",
|
|
178
|
+
"city": "London",
|
|
179
|
+
"postal_code": "SW1A 2AA",
|
|
180
|
+
"country": "United Kingdom"
|
|
166
181
|
}
|
|
167
182
|
])
|
|
168
183
|
```
|
|
@@ -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": "
|
|
85
|
-
"MyId4": "
|
|
86
|
-
"MyId5": "
|
|
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,13 @@ 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
|
+
)
|
|
106
115
|
```
|
|
107
116
|
|
|
108
117
|
### Address components
|
|
@@ -128,6 +137,12 @@ response = client.geocode([
|
|
|
128
137
|
"city": "Toronto",
|
|
129
138
|
"state": "ON",
|
|
130
139
|
"country": "Canada"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"street": "10 Downing St",
|
|
143
|
+
"city": "London",
|
|
144
|
+
"postal_code": "SW1A 2AA",
|
|
145
|
+
"country": "United Kingdom"
|
|
131
146
|
}
|
|
132
147
|
])
|
|
133
148
|
```
|
|
@@ -63,6 +63,7 @@ from geocodio.models import (
|
|
|
63
63
|
StateLegislativeDistrict,
|
|
64
64
|
StatisticsCanadaData,
|
|
65
65
|
Timezone,
|
|
66
|
+
UKLegislativeDistrict,
|
|
66
67
|
ZIP4Data,
|
|
67
68
|
)
|
|
68
69
|
|
|
@@ -379,26 +380,44 @@ class Geocodio:
|
|
|
379
380
|
and response_json["results"]
|
|
380
381
|
and "response" in response_json["results"][0]
|
|
381
382
|
):
|
|
382
|
-
results = [
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
383
|
+
results = []
|
|
384
|
+
for res in response_json["results"]:
|
|
385
|
+
query = res.get("query", "")
|
|
386
|
+
matches = res.get("response", {}).get("results") or []
|
|
387
|
+
|
|
388
|
+
# Unmatched query (e.g. an unparseable address): keep an entry
|
|
389
|
+
# so the result list stays aligned with the submitted addresses,
|
|
390
|
+
# but with no coordinates.
|
|
391
|
+
if not matches:
|
|
392
|
+
results.append(
|
|
393
|
+
GeocodingResult(
|
|
394
|
+
address_components=AddressComponents.from_api({}),
|
|
395
|
+
formatted_address="",
|
|
396
|
+
location=None,
|
|
397
|
+
accuracy=0.0,
|
|
398
|
+
accuracy_type="",
|
|
399
|
+
source="",
|
|
400
|
+
query=query,
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
top = matches[0]
|
|
406
|
+
results.append(
|
|
407
|
+
GeocodingResult(
|
|
408
|
+
address_components=AddressComponents.from_api(
|
|
409
|
+
top["address_components"]
|
|
410
|
+
),
|
|
411
|
+
formatted_address=top["formatted_address"],
|
|
412
|
+
location=Location(**top["location"]),
|
|
413
|
+
accuracy=top.get("accuracy", 0.0),
|
|
414
|
+
accuracy_type=top.get("accuracy_type", ""),
|
|
415
|
+
source=top.get("source", ""),
|
|
416
|
+
query=query,
|
|
417
|
+
fields=self._parse_fields(top.get("fields")),
|
|
418
|
+
stable_address_key=top.get("stable_address_key"),
|
|
419
|
+
)
|
|
399
420
|
)
|
|
400
|
-
for res in response_json["results"]
|
|
401
|
-
]
|
|
402
421
|
return GeocodingResponse(results=results)
|
|
403
422
|
|
|
404
423
|
# Handle single response format
|
|
@@ -413,6 +432,7 @@ class Geocodio:
|
|
|
413
432
|
accuracy_type=res.get("accuracy_type", ""),
|
|
414
433
|
source=res.get("source", ""),
|
|
415
434
|
fields=self._parse_fields(res.get("fields")),
|
|
435
|
+
stable_address_key=res.get("stable_address_key"),
|
|
416
436
|
)
|
|
417
437
|
for res in response_json.get("results", [])
|
|
418
438
|
]
|
|
@@ -452,6 +472,7 @@ class Geocodio:
|
|
|
452
472
|
- acs, acs-demographics, acs-economics, acs-families, acs-housing, acs-social
|
|
453
473
|
- riding, provriding, provriding-next (Canadian data)
|
|
454
474
|
- statcan (Statistics Canada data)
|
|
475
|
+
- uk-westminster, uk-westminster-next, uk-devolved, uk-devolved-next, uk-local, uk-local-next (UK legislative districts)
|
|
455
476
|
- zip4 (ZIP+4 data)
|
|
456
477
|
- ffiec (FFIEC data, beta)
|
|
457
478
|
|
|
@@ -832,6 +853,35 @@ class Geocodio:
|
|
|
832
853
|
else None
|
|
833
854
|
)
|
|
834
855
|
|
|
856
|
+
# United Kingdom fields (each append returns a list of districts). The
|
|
857
|
+
# ``-next`` request variants fold into these same response keys.
|
|
858
|
+
uk_westminster = (
|
|
859
|
+
[
|
|
860
|
+
UKLegislativeDistrict.from_api(district)
|
|
861
|
+
for district in fields_data["uk_westminster"]
|
|
862
|
+
]
|
|
863
|
+
if "uk_westminster" in fields_data
|
|
864
|
+
else None
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
uk_devolved = (
|
|
868
|
+
[
|
|
869
|
+
UKLegislativeDistrict.from_api(district)
|
|
870
|
+
for district in fields_data["uk_devolved"]
|
|
871
|
+
]
|
|
872
|
+
if "uk_devolved" in fields_data
|
|
873
|
+
else None
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
uk_local = (
|
|
877
|
+
[
|
|
878
|
+
UKLegislativeDistrict.from_api(district)
|
|
879
|
+
for district in fields_data["uk_local"]
|
|
880
|
+
]
|
|
881
|
+
if "uk_local" in fields_data
|
|
882
|
+
else None
|
|
883
|
+
)
|
|
884
|
+
|
|
835
885
|
# Collect all known field keys that were parsed
|
|
836
886
|
parsed_keys = {
|
|
837
887
|
"timezone",
|
|
@@ -856,6 +906,9 @@ class Geocodio:
|
|
|
856
906
|
"provriding",
|
|
857
907
|
"provriding-next",
|
|
858
908
|
"statcan",
|
|
909
|
+
"uk_westminster",
|
|
910
|
+
"uk_devolved",
|
|
911
|
+
"uk_local",
|
|
859
912
|
}
|
|
860
913
|
# Add flat census keys that were parsed (census2000, census2020, etc.)
|
|
861
914
|
# All census years are now stored in _census dict for dynamic access
|
|
@@ -878,6 +931,9 @@ class Geocodio:
|
|
|
878
931
|
provriding=provriding,
|
|
879
932
|
provriding_next=provriding_next,
|
|
880
933
|
statcan=statcan,
|
|
934
|
+
uk_westminster=uk_westminster,
|
|
935
|
+
uk_devolved=uk_devolved,
|
|
936
|
+
uk_local=uk_local,
|
|
881
937
|
extras=extras,
|
|
882
938
|
_census=census_data_dict, # All census years stored here
|
|
883
939
|
**acs_fields, # Dynamically include all ACS metric fields
|
|
@@ -309,6 +309,20 @@ class StatisticsCanadaData(ApiModelMixin):
|
|
|
309
309
|
extras: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
@dataclass(slots=True, frozen=True)
|
|
313
|
+
class UKLegislativeDistrict(ApiModelMixin):
|
|
314
|
+
"""UK legislative district returned by the uk-westminster, uk-devolved, and
|
|
315
|
+
uk-local appends (and their ``-next`` variants)."""
|
|
316
|
+
|
|
317
|
+
district_type: Optional[str] = None
|
|
318
|
+
gss_code: Optional[str] = None
|
|
319
|
+
ocd_id: Optional[str] = None
|
|
320
|
+
name: Optional[str] = None
|
|
321
|
+
is_upcoming_district: Optional[bool] = None
|
|
322
|
+
source: Optional[str] = None
|
|
323
|
+
extras: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
324
|
+
|
|
325
|
+
|
|
312
326
|
@dataclass(slots=True, frozen=True)
|
|
313
327
|
class FFIECData(ApiModelMixin):
|
|
314
328
|
"""FFIEC CRA/HMDA Data (Beta)."""
|
|
@@ -373,6 +387,11 @@ class GeocodioFields:
|
|
|
373
387
|
provriding_next: Optional[ProvincialRiding] = None
|
|
374
388
|
statcan: Optional[StatisticsCanadaData] = None
|
|
375
389
|
|
|
390
|
+
# United Kingdom fields
|
|
391
|
+
uk_westminster: Optional[List[UKLegislativeDistrict]] = None
|
|
392
|
+
uk_devolved: Optional[List[UKLegislativeDistrict]] = None
|
|
393
|
+
uk_local: Optional[List[UKLegislativeDistrict]] = None
|
|
394
|
+
|
|
376
395
|
# Catch-all for any future or unknown fields from the API
|
|
377
396
|
extras: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
378
397
|
|
|
@@ -665,11 +684,18 @@ class DistanceJobResponse:
|
|
|
665
684
|
class GeocodingResult:
|
|
666
685
|
address_components: AddressComponents
|
|
667
686
|
formatted_address: str
|
|
668
|
-
location: Location
|
|
687
|
+
location: Optional[Location]
|
|
669
688
|
accuracy: float
|
|
670
689
|
accuracy_type: str
|
|
671
690
|
source: str
|
|
672
691
|
fields: Optional[GeocodioFields] = None
|
|
692
|
+
query: str = ""
|
|
693
|
+
stable_address_key: Optional[str] = None
|
|
694
|
+
|
|
695
|
+
@property
|
|
696
|
+
def matched(self) -> bool:
|
|
697
|
+
"""True when the API returned coordinates for this query."""
|
|
698
|
+
return self.location is not None
|
|
673
699
|
|
|
674
700
|
|
|
675
701
|
@dataclass(slots=True, frozen=True)
|
|
@@ -51,6 +51,32 @@ def test_integration_geocode(client):
|
|
|
51
51
|
assert components.postal_code is not None
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def test_integration_stable_address_key(client):
|
|
55
|
+
"""Verify stable_address_key is exposed on real single, reverse, and batch calls."""
|
|
56
|
+
# Single forward geocode
|
|
57
|
+
single = client.geocode("1109 N Highland St, Arlington, VA").results[0]
|
|
58
|
+
assert single.stable_address_key is not None
|
|
59
|
+
assert isinstance(single.stable_address_key, str)
|
|
60
|
+
assert single.stable_address_key != ""
|
|
61
|
+
|
|
62
|
+
# Reverse geocode (White House)
|
|
63
|
+
reverse = client.reverse((38.897699, -77.036547)).results[0]
|
|
64
|
+
assert reverse.stable_address_key is not None
|
|
65
|
+
assert isinstance(reverse.stable_address_key, str)
|
|
66
|
+
|
|
67
|
+
# Batch geocode (nested response format)
|
|
68
|
+
batch = client.geocode(
|
|
69
|
+
["3730 N Clark St, Chicago, IL", "638 E 13th Ave, Denver, CO"]
|
|
70
|
+
)
|
|
71
|
+
for result in batch.results:
|
|
72
|
+
assert result.stable_address_key is not None
|
|
73
|
+
assert isinstance(result.stable_address_key, str)
|
|
74
|
+
|
|
75
|
+
# The key should be stable: the same address returns the same key.
|
|
76
|
+
again = client.geocode("1109 N Highland St, Arlington, VA").results[0]
|
|
77
|
+
assert again.stable_address_key == single.stable_address_key
|
|
78
|
+
|
|
79
|
+
|
|
54
80
|
def test_integration_reverse(client):
|
|
55
81
|
"""Test real reverse geocoding API call."""
|
|
56
82
|
# Test coordinates (White House)
|
|
@@ -584,6 +610,40 @@ def test_integration_with_canadian_fields(client):
|
|
|
584
610
|
assert isinstance(fields.statcan.census_year, int)
|
|
585
611
|
|
|
586
612
|
|
|
613
|
+
def test_integration_with_uk_fields(client):
|
|
614
|
+
"""Test real API call with United Kingdom legislative district fields."""
|
|
615
|
+
response = client.geocode(
|
|
616
|
+
"10 Downing St, London",
|
|
617
|
+
fields=["uk-westminster", "uk-local"],
|
|
618
|
+
country="United Kingdom", # Country hint so the address resolves to the UK
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# Verify response structure
|
|
622
|
+
assert response is not None
|
|
623
|
+
assert len(response.results) > 0
|
|
624
|
+
result = response.results[0]
|
|
625
|
+
|
|
626
|
+
# Verify fields data
|
|
627
|
+
fields = result.fields
|
|
628
|
+
assert fields is not None
|
|
629
|
+
|
|
630
|
+
# Check Westminster parliamentary constituency
|
|
631
|
+
assert fields.uk_westminster is not None
|
|
632
|
+
assert len(fields.uk_westminster) > 0
|
|
633
|
+
westminster = fields.uk_westminster[0]
|
|
634
|
+
assert westminster.district_type is not None
|
|
635
|
+
assert westminster.name is not None
|
|
636
|
+
assert westminster.gss_code is not None
|
|
637
|
+
assert westminster.ocd_id is not None
|
|
638
|
+
assert isinstance(westminster.is_upcoming_district, bool)
|
|
639
|
+
assert westminster.source is not None
|
|
640
|
+
|
|
641
|
+
# Check local authority ward
|
|
642
|
+
assert fields.uk_local is not None
|
|
643
|
+
assert len(fields.uk_local) > 0
|
|
644
|
+
assert fields.uk_local[0].name is not None
|
|
645
|
+
|
|
646
|
+
|
|
587
647
|
def test_integration_with_census_years(client):
|
|
588
648
|
"""Test real API call with various census years."""
|
|
589
649
|
# Test address
|
|
@@ -34,6 +34,7 @@ def sample_payload() -> dict:
|
|
|
34
34
|
"observes_dst": True,
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
+
"stable_address_key": "abc123stablekey",
|
|
37
38
|
}
|
|
38
39
|
],
|
|
39
40
|
}
|
|
@@ -67,6 +68,8 @@ def test_geocode_single(client, httpx_mock):
|
|
|
67
68
|
tz = resp.results[0].fields.timezone
|
|
68
69
|
assert tz.name == "America/New_York"
|
|
69
70
|
assert tz.observes_dst is True
|
|
71
|
+
# stable address key (v2 returns this on every result)
|
|
72
|
+
assert resp.results[0].stable_address_key == "abc123stablekey"
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
def test_geocode_batch(client, httpx_mock):
|
|
@@ -748,3 +751,255 @@ def test_geocode_with_stateleg_fields(client, httpx_mock):
|
|
|
748
751
|
|
|
749
752
|
# Ensure state_legislative_districts didn't leak into extras
|
|
750
753
|
assert "state_legislative_districts" not in fields.extras
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def test_geocode_with_uk_fields(client, httpx_mock):
|
|
757
|
+
"""Test geocoding a UK address with the UK legislative district appends.
|
|
758
|
+
|
|
759
|
+
The API returns uk_westminster / uk_local as lists of district objects.
|
|
760
|
+
"""
|
|
761
|
+
|
|
762
|
+
def response_callback(request):
|
|
763
|
+
assert request.url.params["fields"] == "uk-westminster,uk-local"
|
|
764
|
+
return httpx.Response(
|
|
765
|
+
200,
|
|
766
|
+
json={
|
|
767
|
+
"results": [
|
|
768
|
+
{
|
|
769
|
+
"address_components": {
|
|
770
|
+
"number": "10",
|
|
771
|
+
"street": "Downing",
|
|
772
|
+
"suffix": "St",
|
|
773
|
+
"city": "London",
|
|
774
|
+
"nation": "England",
|
|
775
|
+
"postal_code": "SW1A 2AA",
|
|
776
|
+
"country": "GB",
|
|
777
|
+
},
|
|
778
|
+
"formatted_address": "10 Downing St, London SW1A 2AA",
|
|
779
|
+
"location": {"lat": 51.503541, "lng": -0.12767},
|
|
780
|
+
"accuracy": 1,
|
|
781
|
+
"accuracy_type": "rooftop",
|
|
782
|
+
"source": "Contains OS data © Crown copyright.",
|
|
783
|
+
"fields": {
|
|
784
|
+
"uk_westminster": [
|
|
785
|
+
{
|
|
786
|
+
"district_type": "westminster_constituency",
|
|
787
|
+
"gss_code": "E14001172",
|
|
788
|
+
"ocd_id": "ocd-division/country:gb/part:eng/region:uki/ed:cities_of_london_and_westminster",
|
|
789
|
+
"name": "Cities of London and Westminster",
|
|
790
|
+
"is_upcoming_district": False,
|
|
791
|
+
"source": "Office for National Statistics",
|
|
792
|
+
}
|
|
793
|
+
],
|
|
794
|
+
"uk_local": [
|
|
795
|
+
{
|
|
796
|
+
"district_type": "ward",
|
|
797
|
+
"gss_code": "E05013806",
|
|
798
|
+
"ocd_id": "ocd-division/country:gb/part:eng/ward:e05013806",
|
|
799
|
+
"name": "St James's",
|
|
800
|
+
"is_upcoming_district": False,
|
|
801
|
+
"source": "Office for National Statistics",
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
},
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
httpx_mock.add_callback(
|
|
811
|
+
callback=response_callback,
|
|
812
|
+
url=httpx.URL(
|
|
813
|
+
"https://api.test/v2/geocode",
|
|
814
|
+
params={
|
|
815
|
+
"q": "10 Downing St, London, United Kingdom",
|
|
816
|
+
"fields": "uk-westminster,uk-local",
|
|
817
|
+
},
|
|
818
|
+
),
|
|
819
|
+
match_headers={"Authorization": "Bearer TEST_KEY"},
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# Act
|
|
823
|
+
resp = client.geocode(
|
|
824
|
+
"10 Downing St, London, United Kingdom",
|
|
825
|
+
fields=["uk-westminster", "uk-local"],
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
# Assert - UK appends should be parsed into typed models, not None
|
|
829
|
+
fields = resp.results[0].fields
|
|
830
|
+
assert fields.uk_westminster is not None
|
|
831
|
+
assert len(fields.uk_westminster) == 1
|
|
832
|
+
assert fields.uk_westminster[0].name == "Cities of London and Westminster"
|
|
833
|
+
assert fields.uk_westminster[0].district_type == "westminster_constituency"
|
|
834
|
+
|
|
835
|
+
assert fields.uk_local is not None
|
|
836
|
+
assert fields.uk_local[0].district_type == "ward"
|
|
837
|
+
assert fields.uk_local[0].name == "St James's"
|
|
838
|
+
|
|
839
|
+
# UK devolved was not requested for this address, so it stays None
|
|
840
|
+
assert fields.uk_devolved is None
|
|
841
|
+
|
|
842
|
+
# Ensure UK fields didn't leak into extras
|
|
843
|
+
assert "uk_westminster" not in fields.extras
|
|
844
|
+
assert "uk_local" not in fields.extras
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def test_geocode_batch_with_unmatched_address(client, httpx_mock):
|
|
848
|
+
"""Batch responses can include queries with no results (e.g. an address
|
|
849
|
+
the API could not match). Those entries must not raise IndexError, and the
|
|
850
|
+
result list stays aligned with the submitted addresses so callers can tell
|
|
851
|
+
which query failed."""
|
|
852
|
+
addresses = ["3730 N Clark St, Chicago, IL", "qwertyuiop asdfghjkl zxcvbnm"]
|
|
853
|
+
|
|
854
|
+
def batch_response_callback(request):
|
|
855
|
+
return httpx.Response(
|
|
856
|
+
200,
|
|
857
|
+
json={
|
|
858
|
+
"results": [
|
|
859
|
+
{
|
|
860
|
+
"query": "3730 N Clark St, Chicago, IL",
|
|
861
|
+
"response": {
|
|
862
|
+
"results": [
|
|
863
|
+
{
|
|
864
|
+
"address_components": {
|
|
865
|
+
"number": "3730",
|
|
866
|
+
"predirectional": "N",
|
|
867
|
+
"street": "Clark",
|
|
868
|
+
"suffix": "St",
|
|
869
|
+
"city": "Chicago",
|
|
870
|
+
"county": "Cook County",
|
|
871
|
+
"state_province": "IL",
|
|
872
|
+
"postal_code": "60613",
|
|
873
|
+
"country": "US",
|
|
874
|
+
},
|
|
875
|
+
"formatted_address": "3730 N Clark St, Chicago, IL 60613",
|
|
876
|
+
"location": {"lat": 41.94987, "lng": -87.65893},
|
|
877
|
+
"accuracy": 1,
|
|
878
|
+
"accuracy_type": "rooftop",
|
|
879
|
+
"source": "Cook",
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
"query": "qwertyuiop asdfghjkl zxcvbnm",
|
|
886
|
+
"response": {
|
|
887
|
+
"error": "Could not geocode address. No matches found.",
|
|
888
|
+
"results": [],
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
]
|
|
892
|
+
},
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
httpx_mock.add_callback(
|
|
896
|
+
callback=batch_response_callback,
|
|
897
|
+
url=httpx.URL("https://api.test/v2/geocode"),
|
|
898
|
+
match_headers={"Authorization": "Bearer TEST_KEY"},
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
resp = client.geocode(addresses)
|
|
902
|
+
|
|
903
|
+
# One entry per submitted address, in order.
|
|
904
|
+
assert len(resp.results) == 2
|
|
905
|
+
|
|
906
|
+
matched, unmatched = resp.results
|
|
907
|
+
assert matched.matched is True
|
|
908
|
+
assert matched.query == "3730 N Clark St, Chicago, IL"
|
|
909
|
+
assert matched.formatted_address == "3730 N Clark St, Chicago, IL 60613"
|
|
910
|
+
assert matched.location.lat == 41.94987
|
|
911
|
+
|
|
912
|
+
# Unmatched query is preserved with no coordinates.
|
|
913
|
+
assert unmatched.matched is False
|
|
914
|
+
assert unmatched.location is None
|
|
915
|
+
assert unmatched.query == "qwertyuiop asdfghjkl zxcvbnm"
|
|
916
|
+
assert unmatched.formatted_address == ""
|
|
917
|
+
|
|
918
|
+
def test_geocode_batch_exposes_stable_address_key(client, httpx_mock):
|
|
919
|
+
"""stable_address_key is parsed from the nested batch response format."""
|
|
920
|
+
addresses = ["1109 N Highland St, Arlington, VA"]
|
|
921
|
+
|
|
922
|
+
def batch_response_callback(request):
|
|
923
|
+
return httpx.Response(
|
|
924
|
+
200,
|
|
925
|
+
json={
|
|
926
|
+
"results": [
|
|
927
|
+
{
|
|
928
|
+
"query": "1109 N Highland St, Arlington, VA",
|
|
929
|
+
"response": {
|
|
930
|
+
"results": [
|
|
931
|
+
{
|
|
932
|
+
"address_components": {
|
|
933
|
+
"number": "1109",
|
|
934
|
+
"street": "Highland",
|
|
935
|
+
"suffix": "St",
|
|
936
|
+
"city": "Arlington",
|
|
937
|
+
"state_province": "VA",
|
|
938
|
+
"postal_code": "22201",
|
|
939
|
+
"country": "US",
|
|
940
|
+
},
|
|
941
|
+
"formatted_address": "1109 N Highland St, Arlington, VA 22201",
|
|
942
|
+
"location": {"lat": 38.886672, "lng": -77.094735},
|
|
943
|
+
"accuracy": 1,
|
|
944
|
+
"accuracy_type": "rooftop",
|
|
945
|
+
"source": "Arlington",
|
|
946
|
+
"stable_address_key": "arlington-stable-key",
|
|
947
|
+
}
|
|
948
|
+
]
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
httpx_mock.add_callback(
|
|
956
|
+
callback=batch_response_callback,
|
|
957
|
+
url=httpx.URL("https://api.test/v2/geocode"),
|
|
958
|
+
match_headers={"Authorization": "Bearer TEST_KEY"},
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
resp = client.geocode(addresses)
|
|
962
|
+
|
|
963
|
+
assert resp.results[0].stable_address_key == "arlington-stable-key"
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
def test_geocode_stable_address_key_absent_is_none(client, httpx_mock):
|
|
967
|
+
"""stable_address_key defaults to None when the API omits it."""
|
|
968
|
+
|
|
969
|
+
def response_callback(request):
|
|
970
|
+
return httpx.Response(
|
|
971
|
+
200,
|
|
972
|
+
json={
|
|
973
|
+
"results": [
|
|
974
|
+
{
|
|
975
|
+
"address_components": {
|
|
976
|
+
"number": "1109",
|
|
977
|
+
"street": "Highland",
|
|
978
|
+
"suffix": "St",
|
|
979
|
+
"city": "Arlington",
|
|
980
|
+
"state_province": "VA",
|
|
981
|
+
"postal_code": "22201",
|
|
982
|
+
"country": "US",
|
|
983
|
+
},
|
|
984
|
+
"formatted_address": "1109 N Highland St, Arlington, VA 22201",
|
|
985
|
+
"location": {"lat": 38.886672, "lng": -77.094735},
|
|
986
|
+
"accuracy": 1,
|
|
987
|
+
"accuracy_type": "rooftop",
|
|
988
|
+
"source": "Arlington",
|
|
989
|
+
}
|
|
990
|
+
],
|
|
991
|
+
},
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
httpx_mock.add_callback(
|
|
995
|
+
callback=response_callback,
|
|
996
|
+
url=httpx.URL(
|
|
997
|
+
"https://api.test/v2/geocode",
|
|
998
|
+
params={"q": "1109 N Highland St, Arlington, VA"},
|
|
999
|
+
),
|
|
1000
|
+
match_headers={"Authorization": "Bearer TEST_KEY"},
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
resp = client.geocode("1109 N Highland St, Arlington, VA")
|
|
1004
|
+
|
|
1005
|
+
assert resp.results[0].stable_address_key is None
|
|
@@ -19,6 +19,7 @@ from geocodio.models import (
|
|
|
19
19
|
StateLegislativeDistrict,
|
|
20
20
|
StatisticsCanadaData,
|
|
21
21
|
Timezone,
|
|
22
|
+
UKLegislativeDistrict,
|
|
22
23
|
ZIP4Data,
|
|
23
24
|
)
|
|
24
25
|
|
|
@@ -338,6 +339,26 @@ def test_canadian_riding():
|
|
|
338
339
|
assert riding.get_extra("extra_field") == "extra value"
|
|
339
340
|
|
|
340
341
|
|
|
342
|
+
def test_uk_legislative_district():
|
|
343
|
+
"""Test UK legislative district data model."""
|
|
344
|
+
data = {
|
|
345
|
+
"district_type": "westminster_constituency",
|
|
346
|
+
"gss_code": "E14001172",
|
|
347
|
+
"ocd_id": "ocd-division/country:gb/part:eng/region:uki/ed:cities_of_london_and_westminster",
|
|
348
|
+
"name": "Cities of London and Westminster",
|
|
349
|
+
"is_upcoming_district": False,
|
|
350
|
+
"source": "Office for National Statistics",
|
|
351
|
+
"extra_field": "extra value",
|
|
352
|
+
}
|
|
353
|
+
district = UKLegislativeDistrict.from_api(data)
|
|
354
|
+
assert district.district_type == "westminster_constituency"
|
|
355
|
+
assert district.gss_code == "E14001172"
|
|
356
|
+
assert district.name == "Cities of London and Westminster"
|
|
357
|
+
assert district.is_upcoming_district is False
|
|
358
|
+
assert district.source == "Office for National Statistics"
|
|
359
|
+
assert district.get_extra("extra_field") == "extra value"
|
|
360
|
+
|
|
361
|
+
|
|
341
362
|
def test_statistics_canada_data():
|
|
342
363
|
"""Test Statistics Canada data model."""
|
|
343
364
|
data = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -149,7 +149,7 @@ wheels = [
|
|
|
149
149
|
|
|
150
150
|
[[package]]
|
|
151
151
|
name = "geocodio-library-python"
|
|
152
|
-
version = "1.
|
|
152
|
+
version = "1.3.0"
|
|
153
153
|
source = { editable = "." }
|
|
154
154
|
dependencies = [
|
|
155
155
|
{ name = "httpx" },
|
|
File without changes
|
{geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/e2e/test_batch_reverse.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_exceptions.py
RENAMED
|
File without changes
|
{geocodio_library_python-1.0.0 → geocodio_library_python-1.3.0}/tests/unit/test_geocoding.py
RENAMED
|
File without changes
|
|
File without changes
|