Skip to content

Commit 89e5a62

Browse files
committed
fix: update geolocation fallback API and use static country code mapping
1 parent b655866 commit 89e5a62

5 files changed

Lines changed: 60 additions & 52 deletions

File tree

codecarbon/external/geography.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"""
44

55
import re
6-
import urllib.parse
76
from dataclasses import dataclass
87
from typing import Callable, Dict, Optional
98

9+
import pycountry
1010
import requests
1111

1212
from codecarbon.core.cloud import get_env_cloud_details
@@ -93,10 +93,14 @@ def from_geo_js(cls, url: str) -> "GeoMetadata":
9393
try:
9494
response: Dict = requests.get(url, timeout=0.5).json()
9595

96+
region = response.get("region", "").lower()
97+
if not region:
98+
raise ValueError("Region is empty")
99+
96100
return cls(
97101
country_iso_code=response["country_code3"].upper(),
98102
country_name=response["country"],
99-
region=response.get("region", "").lower(),
103+
region=region,
100104
latitude=float(response.get("latitude")),
101105
longitude=float(response.get("longitude")),
102106
country_2letter_iso_code=response.get("country_code"),
@@ -107,32 +111,36 @@ def from_geo_js(cls, url: str) -> "GeoMetadata":
107111
f"Unable to access geographical location through primary API. Will resort to using the backup API - Exception : {e} - url={url}"
108112
)
109113

110-
geo_url_backup = "https://ip-api.com/json/"
114+
geo_url_backup = "https://ipinfo.io/json"
111115

112116
try:
113117
geo_response: Dict = requests.get(geo_url_backup, timeout=0.5).json()
114-
country_name = geo_response["country"]
115118

116-
# The previous request does not return the three-letter country code
117-
country_code_3_url = f"https://api.first.org/data/v1/countries?q={urllib.parse.quote_plus(country_name)}&scope=iso"
118-
country_code_response: Dict = requests.get(
119-
country_code_3_url, timeout=0.5
120-
).json()
119+
# extract latitude and longitude from loc (e.g., "loc": "37.4056,-122.0775")
120+
loc = geo_response.get("loc", "").split(",")
121+
latitude = float(loc[0]) if len(loc) == 2 else 0.0
122+
longitude = float(loc[1]) if len(loc) == 2 else 0.0
123+
124+
# Retrieve the 3-letter ISO code using pycountry
125+
country_2letter_iso_code = geo_response.get("country")
126+
country = pycountry.countries.get(alpha_2=country_2letter_iso_code)
127+
128+
# Some countries might not be found or mapped perfectly
129+
country_iso_code = country.alpha_3 if country else ""
130+
country_name = country.name if country else ""
121131

122132
return cls(
123-
country_iso_code=next(
124-
iter(country_code_response["data"].keys())
125-
).upper(),
133+
country_iso_code=country_iso_code.upper(),
126134
country_name=country_name,
127-
region=geo_response.get("regionName", "").lower(),
128-
latitude=float(geo_response.get("lat")),
129-
longitude=float(geo_response.get("lon")),
130-
country_2letter_iso_code=geo_response.get("countryCode"),
135+
region=geo_response.get("region", "").lower(),
136+
latitude=latitude,
137+
longitude=longitude,
138+
country_2letter_iso_code=country_2letter_iso_code,
131139
)
132140
except Exception as e:
133141
# If both API calls fail, default to Canada
134142
logger.warning(
135-
f"Unable to access geographical location. Using 'Canada' as the default value - Exception : {e} - url={url}"
143+
f"Unable to access geographical location through fallback API. Using 'Canada' as the default value - Exception : {e} - url={geo_url_backup}"
136144
)
137145

138146
return cls(

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies = [
4040
"questionary",
4141
"rich",
4242
"typer",
43+
"pycountry",
4344
]
4445

4546
[tool.setuptools.dynamic]

tests/test_geography.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
CLOUD_METADATA_AZURE,
1010
CLOUD_METADATA_GCP,
1111
CLOUD_METADATA_GCP_EMPTY,
12-
COUNTRY_METADATA_USA,
1312
GEO_METADATA_CANADA,
1413
GEO_METADATA_USA,
1514
GEO_METADATA_USA_BACKUP,
@@ -91,14 +90,27 @@ def test_geo_metadata_USA_backup(self):
9190
)
9291
responses.add(
9392
responses.GET,
94-
"https://ip-api.com/json/",
93+
"https://ipinfo.io/json",
9594
json=GEO_METADATA_USA_BACKUP,
9695
status=200,
9796
)
97+
geo = GeoMetadata.from_geo_js(self.geo_js_url)
98+
self.assertEqual("USA", geo.country_iso_code)
99+
self.assertEqual("United States", geo.country_name)
100+
self.assertEqual("illinois", geo.region)
101+
102+
@responses.activate
103+
def test_geo_metadata_empty_region_fallback(self):
104+
empty_region_response = GEO_METADATA_USA.copy()
105+
empty_region_response["region"] = ""
106+
107+
responses.add(
108+
responses.GET, self.geo_js_url, json=empty_region_response, status=200
109+
)
98110
responses.add(
99111
responses.GET,
100-
"https://api.first.org/data/v1/countries?q=United%20States&scope=iso",
101-
json=COUNTRY_METADATA_USA,
112+
"https://ipinfo.io/json",
113+
json=GEO_METADATA_USA_BACKUP,
102114
status=200,
103115
)
104116
geo = GeoMetadata.from_geo_js(self.geo_js_url)

tests/testdata.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,14 @@
1919
}
2020

2121
GEO_METADATA_USA_BACKUP = {
22-
"organization_name": "foobar",
23-
"regionName": "Illinois",
24-
"accuracy": 1,
25-
"asn": 0,
26-
"organization": "foobar",
27-
"timezone": "America/Chicago",
28-
"lon": "88",
29-
"area_code": "0",
3022
"ip": "foobar",
3123
"city": "Chicago",
32-
"country": "United States",
33-
"countryCode": "US",
34-
"lat": "0",
35-
}
36-
37-
COUNTRY_METADATA_USA = {
38-
"status": "OK",
39-
"status-code": 200,
40-
"version": "1.0",
41-
"access": "public",
42-
"data": {
43-
"USA": {
44-
"id": "USA",
45-
"country": "United States of America (the)",
46-
"region": "North America",
47-
},
48-
"UMI": {
49-
"id": "UMI",
50-
"country": "United States Minor Outlying Islands (the)",
51-
"region": "Oceania",
52-
},
53-
},
24+
"region": "Illinois",
25+
"country": "US",
26+
"loc": "0,88",
27+
"org": "foobar",
28+
"postal": "60601",
29+
"timezone": "America/Chicago",
5430
}
5531

5632
GEO_METADATA_CANADA = {

uv.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)