diff --git a/src/geocodio/client.py b/src/geocodio/client.py index fdeabe0..b74147f 100644 --- a/src/geocodio/client.py +++ b/src/geocodio/client.py @@ -414,6 +414,7 @@ def _parse_geocoding_response(self, response_json: dict) -> GeocodingResponse: source=top.get("source", ""), query=query, fields=self._parse_fields(top.get("fields")), + stable_address_key=top.get("stable_address_key"), ) ) return GeocodingResponse(results=results) @@ -430,6 +431,7 @@ def _parse_geocoding_response(self, response_json: dict) -> GeocodingResponse: accuracy_type=res.get("accuracy_type", ""), source=res.get("source", ""), fields=self._parse_fields(res.get("fields")), + stable_address_key=res.get("stable_address_key"), ) for res in response_json.get("results", []) ] diff --git a/src/geocodio/models.py b/src/geocodio/models.py index 3eca092..c4085a2 100644 --- a/src/geocodio/models.py +++ b/src/geocodio/models.py @@ -671,6 +671,7 @@ class GeocodingResult: source: str fields: Optional[GeocodioFields] = None query: str = "" + stable_address_key: Optional[str] = None @property def matched(self) -> bool: diff --git a/tests/e2e/test_api.py b/tests/e2e/test_api.py index 2036886..5a661b3 100644 --- a/tests/e2e/test_api.py +++ b/tests/e2e/test_api.py @@ -51,6 +51,32 @@ def test_integration_geocode(client): assert components.postal_code is not None +def test_integration_stable_address_key(client): + """Verify stable_address_key is exposed on real single, reverse, and batch calls.""" + # Single forward geocode + single = client.geocode("1109 N Highland St, Arlington, VA").results[0] + assert single.stable_address_key is not None + assert isinstance(single.stable_address_key, str) + assert single.stable_address_key != "" + + # Reverse geocode (White House) + reverse = client.reverse((38.897699, -77.036547)).results[0] + assert reverse.stable_address_key is not None + assert isinstance(reverse.stable_address_key, str) + + # Batch geocode (nested response format) + batch = client.geocode( + ["3730 N Clark St, Chicago, IL", "638 E 13th Ave, Denver, CO"] + ) + for result in batch.results: + assert result.stable_address_key is not None + assert isinstance(result.stable_address_key, str) + + # The key should be stable: the same address returns the same key. + again = client.geocode("1109 N Highland St, Arlington, VA").results[0] + assert again.stable_address_key == single.stable_address_key + + def test_integration_reverse(client): """Test real reverse geocoding API call.""" # Test coordinates (White House) diff --git a/tests/unit/test_geocode.py b/tests/unit/test_geocode.py index ae24bc5..ce8915b 100644 --- a/tests/unit/test_geocode.py +++ b/tests/unit/test_geocode.py @@ -34,6 +34,7 @@ def sample_payload() -> dict: "observes_dst": True, } }, + "stable_address_key": "abc123stablekey", } ], } @@ -67,6 +68,8 @@ def response_callback(request): tz = resp.results[0].fields.timezone assert tz.name == "America/New_York" assert tz.observes_dst is True + # stable address key (v2 returns this on every result) + assert resp.results[0].stable_address_key == "abc123stablekey" def test_geocode_batch(client, httpx_mock): @@ -820,3 +823,92 @@ def batch_response_callback(request): assert unmatched.location is None assert unmatched.query == "qwertyuiop asdfghjkl zxcvbnm" assert unmatched.formatted_address == "" + +def test_geocode_batch_exposes_stable_address_key(client, httpx_mock): + """stable_address_key is parsed from the nested batch response format.""" + addresses = ["1109 N Highland St, Arlington, VA"] + + def batch_response_callback(request): + return httpx.Response( + 200, + json={ + "results": [ + { + "query": "1109 N Highland St, Arlington, VA", + "response": { + "results": [ + { + "address_components": { + "number": "1109", + "street": "Highland", + "suffix": "St", + "city": "Arlington", + "state_province": "VA", + "postal_code": "22201", + "country": "US", + }, + "formatted_address": "1109 N Highland St, Arlington, VA 22201", + "location": {"lat": 38.886672, "lng": -77.094735}, + "accuracy": 1, + "accuracy_type": "rooftop", + "source": "Arlington", + "stable_address_key": "arlington-stable-key", + } + ] + }, + }, + ] + }, + ) + + httpx_mock.add_callback( + callback=batch_response_callback, + url=httpx.URL("https://api.test/v2/geocode"), + match_headers={"Authorization": "Bearer TEST_KEY"}, + ) + + resp = client.geocode(addresses) + + assert resp.results[0].stable_address_key == "arlington-stable-key" + + +def test_geocode_stable_address_key_absent_is_none(client, httpx_mock): + """stable_address_key defaults to None when the API omits it.""" + + def response_callback(request): + return httpx.Response( + 200, + json={ + "results": [ + { + "address_components": { + "number": "1109", + "street": "Highland", + "suffix": "St", + "city": "Arlington", + "state_province": "VA", + "postal_code": "22201", + "country": "US", + }, + "formatted_address": "1109 N Highland St, Arlington, VA 22201", + "location": {"lat": 38.886672, "lng": -77.094735}, + "accuracy": 1, + "accuracy_type": "rooftop", + "source": "Arlington", + } + ], + }, + ) + + httpx_mock.add_callback( + callback=response_callback, + url=httpx.URL( + "https://api.test/v2/geocode", + params={"q": "1109 N Highland St, Arlington, VA"}, + ), + match_headers={"Authorization": "Bearer TEST_KEY"}, + ) + + resp = client.geocode("1109 N Highland St, Arlington, VA") + + assert resp.results[0].stable_address_key is None