Skip to content

Commit 3572c14

Browse files
committed
Add local JSON as data source
1 parent 98769ff commit 3572c14

File tree

5 files changed

+112
-4
lines changed

5 files changed

+112
-4
lines changed

app/data/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""app.data"""
22
from ..services.location.csbs import CSBSLocationService
33
from ..services.location.jhu import JhuLocationService
4+
from ..services.location.localjson import LocalJsonLocationService
45
from ..services.location.nyt import NYTLocationService
56

67
# Mapping of services to data-sources.
78
DATA_SOURCES = {
9+
"localjson": LocalJsonLocationService(),
810
"jhu": JhuLocationService(),
911
"csbs": CSBSLocationService(),
1012
"nyt": NYTLocationService(),

app/data/locations.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

app/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def add_datasource(request: Request, call_next):
7474
Attach the data source to the request.state.
7575
"""
7676
# Retrieve the datas ource from query param.
77-
source = data_source(request.query_params.get("source", default="jhu"))
77+
source = data_source(request.query_params.get("source", default="localjson"))
7878

7979
# Abort with 404 if source cannot be found.
8080
if not source:

app/routers/v2.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Sources(str, enum.Enum):
1414
A source available for retrieving data.
1515
"""
1616

17+
LOCALJSON = "localjson"
1718
JHU = "jhu"
1819
CSBS = "csbs"
1920
NYT = "nyt"
@@ -40,11 +41,10 @@ async def get_latest(
4041
@V2.get("/locations", response_model=LocationsResponse, response_model_exclude_unset=True)
4142
async def get_locations(
4243
request: Request,
43-
source: Sources = "jhu",
44+
source: Sources = Sources.LOCALJSON,
4445
country_code: str = None,
4546
province: str = None,
4647
county: str = None,
47-
timelines: bool = False,
4848
):
4949
"""
5050
Getting the locations.
@@ -86,7 +86,7 @@ async def get_locations(
8686
"deaths": sum(map(lambda location: location.deaths, locations)),
8787
"recovered": sum(map(lambda location: location.recovered, locations)),
8888
},
89-
"locations": [location.serialize(timelines) for location in locations],
89+
"locations": [location.serialize() for location in locations],
9090
}
9191

9292

app/services/location/localjson.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""app.services.location.localjson.py"""
2+
import csv
3+
import logging
4+
import os
5+
from datetime import datetime
6+
from pprint import pformat as pf
7+
8+
from asyncache import cached
9+
from cachetools import TTLCache
10+
11+
import app.io
12+
from ...caches import check_cache, load_cache
13+
from ...coordinates import Coordinates
14+
from ...location import TimelinedLocation
15+
from ...models import Timeline
16+
from ...utils import countries
17+
from ...utils import date as date_util
18+
from ...utils import httputils
19+
from . import LocationService
20+
21+
LOGGER = logging.getLogger("services.location.localjson")
22+
PID = os.getpid()
23+
24+
class LocalJsonLocationService(LocationService):
25+
"""
26+
Service for retrieving locations from local JSON file.
27+
"""
28+
29+
async def get_all(self):
30+
# Get the locations.
31+
locations = await get_locations()
32+
return locations
33+
34+
async def get(self, loc_id): # pylint: disable=arguments-differ
35+
# Get location at the index equal to provided id.
36+
locations = await self.get_all()
37+
38+
return locations[loc_id]
39+
40+
41+
# ---------------------------------------------------------------
42+
43+
44+
LOCATIONS_PATH = "locations.json"
45+
46+
47+
@cached(cache=TTLCache(maxsize=1, ttl=1800))
48+
async def get_locations():
49+
"""
50+
Retrieves the locations from the categories. The locations are cached for 1 hour.
51+
52+
:returns: The locations.
53+
:rtype: List[Location]
54+
"""
55+
56+
data_id = "localjson.locations"
57+
LOGGER.info(f"pid:{PID}: {data_id} Requesting data...")
58+
59+
# Transform JSON to list of object
60+
raw_locations: list[dict] = app.io.load(LOCATIONS_PATH)
61+
locations: list[TimelinedLocation] = []
62+
63+
for _, raw_location in enumerate(raw_locations):
64+
coordinates = raw_location.get("coordinates", {})
65+
timelines = raw_location.get("timelines", {})
66+
confirmed_timelines = timelines.get("confirmed", {}).get("timeline", {})
67+
deaths_timelines = timelines.get("deaths", {}).get("timeline", {})
68+
recovered_timelines = timelines.get("recovered", {}).get("timeline", {})
69+
70+
locations.append(
71+
TimelinedLocation(
72+
id=raw_location.get("id"),
73+
country=raw_location.get("country"),
74+
province=raw_location.get("province"),
75+
coordinates=Coordinates(
76+
latitude=coordinates.get("latitude"),
77+
longitude=coordinates.get("longitude")
78+
),
79+
last_updated=datetime.utcnow().isoformat() + "Z",
80+
timelines={
81+
"confirmed": Timeline(
82+
timeline={
83+
date: amount
84+
for date, amount in confirmed_timelines.items()
85+
}
86+
),
87+
"deaths": Timeline(
88+
timeline={
89+
date: amount
90+
for date, amount in deaths_timelines.items()
91+
}
92+
),
93+
"recovered": Timeline(
94+
timeline={
95+
date: amount
96+
for date, amount in recovered_timelines.items()
97+
}
98+
),
99+
},
100+
)
101+
)
102+
103+
LOGGER.info(f"{data_id} Data normalized")
104+
105+
return locations

0 commit comments

Comments
 (0)