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.
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/CHANGELOG.md +15 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/PKG-INFO +79 -6
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/README.md +77 -4
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/pyproject.toml +1 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/_version.py +1 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/client.py +104 -24
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/models.py +156 -1
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_api.py +116 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_geocode.py +468 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_models.py +128 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/uv.lock +2 -2
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/ci.yml +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/publish.yml +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/test-act-event-ci.json +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.github/workflows/test-act-event-publish.json +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/.gitignore +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/LICENSE +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/requirements-dev.txt +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/setup.cfg +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/smoke.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/smoke_lists.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/src/geocodio/exceptions.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/test_pypi_release.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/conftest.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_batch_reverse.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/e2e/test_lists_api.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/test_workflows.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/__init__.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_client.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_distance.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_errors.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_exceptions.py +0 -0
- {geocodio_library_python-1.0.0 → geocodio_library_python-1.4.0}/tests/unit/test_geocoding.py +0 -0
- {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.
|
|
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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: geocodio-library-python
|
|
3
|
-
Version: 1.
|
|
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": "
|
|
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,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": "
|
|
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,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
|
```
|
|
@@ -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(
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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(
|
|
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
|