Skip to content

Commit c05f6c5

Browse files
fix(reverse): fix batch reverse geocoding coordinate format
The library was sending coordinates in incorrect format for batch reverse geocoding operations, causing API errors. Coordinates are now properly converted to strings before sending to the API. - Convert tuple coordinates to comma-separated strings - Send coordinates as plain array instead of wrapped object - Support mixed input formats (tuples and strings)
1 parent 31a3abc commit c05f6c5

File tree

3 files changed

+160
-31
lines changed

3 files changed

+160
-31
lines changed

src/geocodio/client.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,18 @@ def reverse(
124124
params["limit"] = int(limit)
125125

126126
endpoint: str
127-
data: Dict[str, list] | None
127+
data: Union[List[str], None]
128128

129129
# Batch vs single coordinate
130130
if isinstance(coordinate, list):
131131
endpoint = f"{self.BASE_PATH}/reverse"
132-
data = {"coordinates": coordinate}
132+
coords_as_strings = []
133+
for coord in coordinate:
134+
if isinstance(coord, tuple):
135+
coords_as_strings.append(f"{coord[0]},{coord[1]}")
136+
else:
137+
coords_as_strings.append(coord)
138+
data = coords_as_strings
133139
else:
134140
endpoint = f"{self.BASE_PATH}/reverse"
135141
if isinstance(coordinate, tuple):

tests/e2e/test_batch_reverse.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
End-to-end tests for batch reverse geocoding functionality.
3+
"""
4+
5+
import pytest
6+
from geocodio import GeocodioClient
7+
8+
9+
def test_batch_reverse_geocoding(client):
10+
"""Test batch reverse geocoding against real API."""
11+
# Arrange
12+
coordinates = [
13+
(38.886665, -77.094733), # Arlington, VA
14+
(38.897676, -77.036530), # White House
15+
(37.331669, -122.030090) # Apple Park
16+
]
17+
18+
# Act
19+
response = client.reverse(coordinates)
20+
21+
# Assert
22+
assert response is not None
23+
assert len(response.results) == 3
24+
25+
# Check first result (Arlington, VA)
26+
arlington = response.results[0]
27+
assert "Arlington" in arlington.formatted_address
28+
assert "VA" in arlington.formatted_address
29+
assert arlington.location.lat == pytest.approx(38.886672, abs=0.001)
30+
assert arlington.location.lng == pytest.approx(-77.094735, abs=0.001)
31+
32+
# Check second result (White House)
33+
white_house = response.results[1]
34+
assert "Pennsylvania" in white_house.formatted_address
35+
assert "Washington" in white_house.formatted_address or "DC" in white_house.formatted_address
36+
37+
# Check third result (Apple Park)
38+
apple_park = response.results[2]
39+
assert "Cupertino" in apple_park.formatted_address or "CA" in apple_park.formatted_address
40+
41+
42+
def test_batch_reverse_with_strings(client):
43+
"""Test batch reverse geocoding with string coordinates."""
44+
# Arrange
45+
coordinates = [
46+
"38.886665,-77.094733", # Arlington, VA
47+
"38.897676,-77.036530" # White House
48+
]
49+
50+
# Act
51+
response = client.reverse(coordinates)
52+
53+
# Assert
54+
assert response is not None
55+
assert len(response.results) == 2
56+
assert "Arlington" in response.results[0].formatted_address
57+
assert "Pennsylvania" in response.results[1].formatted_address or "Washington" in response.results[1].formatted_address
58+
59+
60+
def test_batch_reverse_with_fields(client):
61+
"""Test batch reverse geocoding with additional fields."""
62+
# Arrange
63+
coordinates = [
64+
(38.886665, -77.094733), # Arlington, VA
65+
(38.897676, -77.036530) # White House
66+
]
67+
68+
# Act
69+
response = client.reverse(coordinates, fields=["timezone", "cd"])
70+
71+
# Assert
72+
assert response is not None
73+
assert len(response.results) == 2
74+
75+
# Check that fields are populated
76+
for result in response.results:
77+
assert result.fields is not None
78+
if result.fields.timezone:
79+
assert result.fields.timezone.name is not None
80+
if result.fields.congressional_districts:
81+
assert len(result.fields.congressional_districts) > 0
82+
83+
84+
def test_empty_batch_reverse(client):
85+
"""Test batch reverse geocoding with empty list."""
86+
# Arrange
87+
coordinates = []
88+
89+
# Act & Assert
90+
with pytest.raises(Exception):
91+
client.reverse(coordinates)
92+
93+
94+
def test_mixed_batch_reverse_formats(client):
95+
"""Test batch reverse geocoding with mixed coordinate formats."""
96+
# Note: The API expects consistent format, so this tests error handling
97+
# Arrange
98+
coordinates = [
99+
(38.886665, -77.094733), # Tuple format
100+
"38.897676,-77.036530" # String format
101+
]
102+
103+
# Act
104+
# The library should handle converting these to a consistent format
105+
response = client.reverse(coordinates)
106+
107+
# Assert
108+
assert response is not None
109+
assert len(response.results) == 2

tests/unit/test_reverse.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,37 +58,51 @@ def batch_response_callback(request):
5858
return httpx.Response(200, json={
5959
"results": [
6060
{
61-
"address_components": {
62-
"number": "1109",
63-
"predirectional": "N",
64-
"street": "Highland",
65-
"suffix": "St",
66-
"formatted_street": "N Highland St",
67-
"city": "Arlington",
68-
"state": "VA",
69-
"zip": "22201"
70-
},
71-
"formatted_address": "1109 N Highland St, Arlington, VA 22201",
72-
"location": {"lat": 38.886672, "lng": -77.094735},
73-
"accuracy": 1,
74-
"accuracy_type": "rooftop",
75-
"source": "Arlington"
61+
"query": "38.886672,-77.094735",
62+
"response": {
63+
"results": [
64+
{
65+
"address_components": {
66+
"number": "1109",
67+
"predirectional": "N",
68+
"street": "Highland",
69+
"suffix": "St",
70+
"formatted_street": "N Highland St",
71+
"city": "Arlington",
72+
"state": "VA",
73+
"zip": "22201"
74+
},
75+
"formatted_address": "1109 N Highland St, Arlington, VA 22201",
76+
"location": {"lat": 38.886672, "lng": -77.094735},
77+
"accuracy": 1,
78+
"accuracy_type": "rooftop",
79+
"source": "Arlington"
80+
}
81+
]
82+
}
7683
},
7784
{
78-
"address_components": {
79-
"number": "1600",
80-
"street": "Pennsylvania",
81-
"suffix": "Ave",
82-
"postdirectional": "NW",
83-
"city": "Washington",
84-
"state": "DC",
85-
"zip": "20500"
86-
},
87-
"formatted_address": "1600 Pennsylvania Ave NW, Washington, DC 20500",
88-
"location": {"lat": 38.898719, "lng": -77.036547},
89-
"accuracy": 1,
90-
"accuracy_type": "rooftop",
91-
"source": "DC"
85+
"query": "38.898719,-77.036547",
86+
"response": {
87+
"results": [
88+
{
89+
"address_components": {
90+
"number": "1600",
91+
"street": "Pennsylvania",
92+
"suffix": "Ave",
93+
"postdirectional": "NW",
94+
"city": "Washington",
95+
"state": "DC",
96+
"zip": "20500"
97+
},
98+
"formatted_address": "1600 Pennsylvania Ave NW, Washington, DC 20500",
99+
"location": {"lat": 38.898719, "lng": -77.036547},
100+
"accuracy": 1,
101+
"accuracy_type": "rooftop",
102+
"source": "DC"
103+
}
104+
]
105+
}
92106
}
93107
]
94108
})

0 commit comments

Comments
 (0)