11"""
22app.main.py
33"""
4- import datetime as dt
5- import enum
64import logging
75import os
86import reprlib
9- from typing import Dict , List
7+ import datetime as dt
108
11- import fastapi
129import pydantic
1310import 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# ############
3729LOGGER = 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 )
8880async 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 @ /
0 commit comments