Skip to content

Commit 97d98a0

Browse files
committed
feat: ✨ zipcode resolver
1 parent 83cdddd commit 97d98a0

7 files changed

Lines changed: 90 additions & 4 deletions

File tree

app/api/router.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
from fastapi import APIRouter
77

8-
from app.api.routes import forms, templates, weather
8+
from app.api.routes import forms, templates, weather, zipcode
99

1010
api_router = APIRouter()
1111
api_router.include_router(templates.router)
1212
api_router.include_router(forms.router)
13-
api_router.include_router(weather.router)
13+
api_router.include_router(weather.router)
14+
api_router.include_router(zipcode.router)

app/api/routes/zipcode.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from fastapi import APIRouter
2+
3+
from app.core.errors.base import AppError
4+
from app.services.controller import Controller
5+
6+
router = APIRouter(prefix="/zipcode", tags=["zipcode"])
7+
8+
@router.get("/postal-code")
9+
def get_postal_code(
10+
country: str,
11+
postal_code: str,
12+
):
13+
"""
14+
Fetch data for the given postal code.
15+
16+
Query params:
17+
- country: required string
18+
- postal_code: required string
19+
"""
20+
controller = Controller()
21+
try:
22+
return controller.get_postal_code(country, postal_code)
23+
except Exception as e:
24+
raise AppError(str(e), status_code=500)
25+
26+
@router.get("/location")
27+
def get_location(
28+
country: str,
29+
city: str,
30+
):
31+
"""
32+
Fetch data for the given location.
33+
34+
Query params:
35+
- country: required string
36+
- city: required string
37+
"""
38+
controller = Controller()
39+
try:
40+
return controller.get_location(country, city)
41+
except Exception as e:
42+
raise AppError(str(e), status_code=500)

app/services/controller.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ def prepare_fillable(self, pdf_path: str):
1414
return self.file_manipulator.prepare_fillable(pdf_path)
1515

1616
def get_weather(self, latitude: float, longitude: float, start_date: str, end_date: str, hourly_fields: list = None):
17-
return self.external_apis_coordinator.get_weather(latitude, longitude, start_date, end_date, hourly_fields)
17+
return self.external_apis_coordinator.get_weather(latitude, longitude, start_date, end_date, hourly_fields)
18+
19+
def get_postal_code(self, country: str, postal_code: str):
20+
return self.external_apis_coordinator.get_postal_code(country, postal_code)
21+
22+
def get_location(self, country: str, city: str):
23+
return self.external_apis_coordinator.get_location(country, city)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pgeocode
2+
import numpy as np
3+
import pandas as pd
4+
5+
class ZipCodeAPI:
6+
def __init__(self):
7+
pass
8+
9+
def get_postal_code(self, country: str, postal_code: str):
10+
res = pgeocode.Nominatim(country).query_postal_code(postal_code)
11+
if isinstance(res, pd.Series):
12+
res = res.replace({np.nan: None}).to_dict()
13+
elif isinstance(res, pd.DataFrame):
14+
res = res.replace({np.nan: None}).to_dict(orient='records')
15+
return res
16+
17+
def get_location(self, country: str, city: str):
18+
res = pgeocode.Nominatim(country).query_location(city)
19+
if isinstance(res, pd.Series):
20+
res = res.replace({np.nan: None}).to_dict()
21+
elif isinstance(res, pd.DataFrame):
22+
res = res.replace({np.nan: None}).to_dict(orient='records')
23+
return res
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
from app.services.external_apis.zipcode_api import ZipCodeAPI
12
from app.services.external_apis.weather_api import WeatherAPI
23

34
class ExternalAPIsCoordinator:
45
def __init__(self):
56
self.weather_api = WeatherAPI()
7+
self.zipcode_api = ZipCodeAPI()
68

79
def get_weather(self, latitude: float, longitude: float, start_date: str, end_date: str, hourly_fields: list[str] | None = None):
810
return self.weather_api.get_weather(latitude, longitude, start_date, end_date, hourly_fields)
11+
12+
def get_postal_code(self, country: str, postal_code: str):
13+
return self.zipcode_api.get_postal_code(country, postal_code)
14+
15+
def get_location(self, country: str, city: str):
16+
return self.zipcode_api.get_location(country, city)

examples/pgeocode_demo.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import pgeocode
2+
3+
nomi = pgeocode.Nominatim('us')
4+
response_pgeo = nomi.query_location("Santa Cruz", top_k=3)
5+
print(response_pgeo)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ python-multipart
1515
openmeteo-requests
1616
requests-cache
1717
retry-requests
18-
pandas
18+
pandas
19+
pgeocode

0 commit comments

Comments
 (0)