Skip to content

Commit f9cf1aa

Browse files
authored
Merge pull request #184 from ExpDev07/refactoring
Refactoring
2 parents 8fddcdd + 11eaa0e commit f9cf1aa

File tree

11 files changed

+186
-161
lines changed

11 files changed

+186
-161
lines changed

app/enums/sources.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from enum import Enum
2+
3+
class Sources(str, Enum):
4+
"""
5+
A source available for retrieving data.
6+
"""
7+
jhu = 'jhu'
8+
csbs = 'csbs'

app/main.py

Lines changed: 18 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
11
"""
22
app.main.py
33
"""
4-
import datetime as dt
5-
import enum
64
import logging
75
import os
86
import reprlib
9-
from typing import Dict, List
7+
import datetime as dt
108

11-
import fastapi
129
import pydantic
1310
import uvicorn
14-
from fastapi.middleware.wsgi import WSGIMiddleware
15-
from fastapi.middleware.cors import CORSMiddleware
1611

17-
from . import models
18-
from .core import create_app
19-
from .data import data_source, data_sources
12+
from fastapi import FastAPI
13+
from fastapi import Request, Response
2014

21-
# ################
22-
# Dependencies
23-
# ################
15+
from fastapi.responses import JSONResponse
2416

17+
from fastapi.middleware.wsgi import WSGIMiddleware
18+
from fastapi.middleware.cors import CORSMiddleware
2519

26-
class Sources(str, enum.Enum):
27-
"""
28-
A source available for retrieving data.
29-
"""
30-
jhu = 'jhu'
31-
csbs = 'csbs'
20+
from .core import create_app
21+
from .data import data_source
3222

23+
from .models.location import LocationResponse as Location, LocationsResponse as Locations
24+
from .models.latest import LatestResponse as Latest
3325

3426
# ############
3527
# FastAPI App
3628
# ############
3729
LOGGER = logging.getLogger('api')
3830

39-
APP = fastapi.FastAPI(
31+
APP = FastAPI(
4032
title='Coronavirus Tracker',
4133
description='API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak. Project page: https://github.com/ExpDev07/coronavirus-tracker-api.',
4234
version='2.0.1',
@@ -59,7 +51,7 @@ class Sources(str, enum.Enum):
5951

6052
# TODO this could probably just be a FastAPI dependency.
6153
@APP.middleware('http')
62-
async def add_datasource(request: fastapi.Request, call_next):
54+
async def add_datasource(request: Request, call_next):
6355
"""
6456
Attach the data source to the request.state.
6557
"""
@@ -68,7 +60,7 @@ async def add_datasource(request: fastapi.Request, call_next):
6860

6961
# Abort with 404 if source cannot be found.
7062
if not source:
71-
return fastapi.Response('The provided data-source was not found.', status_code=404)
63+
return Response('The provided data-source was not found.', status_code=404)
7264

7365
# Attach source to request.
7466
request.state.source = source
@@ -86,104 +78,22 @@ async def add_datasource(request: fastapi.Request, call_next):
8678

8779
@APP.exception_handler(pydantic.error_wrappers.ValidationError)
8880
async def handle_validation_error(
89-
request: fastapi.Request, exc: pydantic.error_wrappers.ValidationError
81+
request: Request, exc: pydantic.error_wrappers.ValidationError
9082
):
9183
"""
9284
Handles validation errors.
9385
"""
94-
return fastapi.responses.JSONResponse({'message': exc.errors()}, status_code=422)
86+
return JSONResponse({'message': exc.errors()}, status_code=422)
9587

9688

9789
# ################
98-
# Routes
90+
# Routing
9991
# ################
10092

101-
V2 = fastapi.APIRouter()
102-
103-
104-
@V2.get('/latest', response_model=models.LatestResponse)
105-
def get_latest(request: fastapi.Request, source: Sources = 'jhu'):
106-
"""
107-
Getting latest amount of total confirmed cases, deaths, and recoveries.
108-
"""
109-
locations = request.state.source.get_all()
110-
return {
111-
'latest': {
112-
'confirmed': sum(map(lambda location: location.confirmed, locations)),
113-
'deaths' : sum(map(lambda location: location.deaths, locations)),
114-
'recovered': sum(map(lambda location: location.recovered, locations)),
115-
}
116-
}
117-
118-
119-
@V2.get(
120-
'/locations', response_model=models.LocationsResponse, response_model_exclude_unset=True
121-
)
122-
def get_locations(
123-
request: fastapi.Request,
124-
source: Sources = 'jhu',
125-
country_code: str = None,
126-
province: str = None,
127-
county: str = None,
128-
timelines: bool = False,
129-
):
130-
"""
131-
Getting the locations.
132-
"""
133-
# All query paramameters.
134-
params = dict(request.query_params)
135-
136-
# Remove reserved params.
137-
params.pop('source', None)
138-
params.pop('timelines', None)
139-
140-
# Retrieve all the locations.
141-
locations = request.state.source.get_all()
142-
143-
# Attempt to filter out locations with properties matching the provided query params.
144-
for key, value in params.items():
145-
# Clean keys for security purposes.
146-
key = key.lower()
147-
value = value.lower().strip('__')
148-
149-
# Do filtering.
150-
try:
151-
locations = [location for location in locations if str(getattr(location, key)).lower() == str(value)]
152-
except AttributeError:
153-
pass
154-
155-
# Return final serialized data.
156-
return {
157-
'latest': {
158-
'confirmed': sum(map(lambda location: location.confirmed, locations)),
159-
'deaths' : sum(map(lambda location: location.deaths, locations)),
160-
'recovered': sum(map(lambda location: location.recovered, locations)),
161-
},
162-
'locations': [location.serialize(timelines) for location in locations],
163-
}
164-
165-
166-
@V2.get('/locations/{id}', response_model=models.LocationResponse)
167-
def get_location_by_id(request: fastapi.Request, id: int, source: Sources = 'jhu', timelines: bool = True):
168-
"""
169-
Getting specific location by id.
170-
"""
171-
return {
172-
'location': request.state.source.get(id).serialize(timelines)
173-
}
174-
175-
176-
@V2.get('/sources')
177-
async def sources():
178-
"""
179-
Retrieves a list of data-sources that are availble to use.
180-
"""
181-
return {
182-
'sources': list(data_sources.keys())
183-
}
93+
from .router import router
18494

18595
# Include routers.
186-
APP.include_router(V2, prefix='/v2', tags=['v2'])
96+
APP.include_router(router, prefix='/v2', tags=['v2'])
18797

18898
# mount the existing Flask app
18999
# v1 @ /

app/models.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

app/models/latest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from pydantic import BaseModel
2+
3+
class Latest(BaseModel):
4+
"""
5+
Latest model.
6+
"""
7+
confirmed: int
8+
deaths: int
9+
recovered: int
10+
11+
class LatestResponse(BaseModel):
12+
"""
13+
Response for latest.
14+
"""
15+
latest: Latest

app/models/location.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from pydantic import BaseModel
2+
from typing import Dict, List
3+
from .timeline import Timelines
4+
from .latest import Latest
5+
6+
class Location(BaseModel):
7+
"""
8+
Location model.
9+
"""
10+
id: int
11+
country: str
12+
country_code: str
13+
county: str = ''
14+
province: str = ''
15+
last_updated: str # TODO use datetime.datetime type.
16+
coordinates: Dict
17+
latest: Latest
18+
timelines: Timelines = {}
19+
20+
class LocationResponse(BaseModel):
21+
"""
22+
Response for location.
23+
"""
24+
location: Location
25+
26+
class LocationsResponse(BaseModel):
27+
"""
28+
Response for locations.
29+
"""
30+
latest: Latest
31+
locations: List[Location] = []

app/models/timeline.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pydantic import BaseModel
2+
from typing import Dict
3+
4+
class Timeline(BaseModel):
5+
"""
6+
Timeline model.
7+
"""
8+
9+
latest: int
10+
timeline: Dict[str, int] = {}
11+
12+
class Timelines(BaseModel):
13+
"""
14+
Timelines model.
15+
"""
16+
confirmed: Timeline
17+
deaths: Timeline
18+
recovered: Timeline

app/router/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from fastapi import APIRouter
2+
3+
# Create the router.
4+
router = APIRouter()
5+
6+
# The routes.
7+
from . import latest, sources, locations

app/router/latest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from fastapi import Request
2+
from . import router
3+
from ..enums.sources import Sources
4+
from ..models.latest import LatestResponse as Latest
5+
6+
@router.get('/latest', response_model=Latest)
7+
def get_latest(request: Request, source: Sources = 'jhu'):
8+
"""
9+
Getting latest amount of total confirmed cases, deaths, and recoveries.
10+
"""
11+
locations = request.state.source.get_all()
12+
return {
13+
'latest': {
14+
'confirmed': sum(map(lambda location: location.confirmed, locations)),
15+
'deaths' : sum(map(lambda location: location.deaths, locations)),
16+
'recovered': sum(map(lambda location: location.recovered, locations)),
17+
}
18+
}

0 commit comments

Comments
 (0)