Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
~~~~~~~~~~~~~~~~~~~~~~~~
API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak.
"""
import logging

# See PEP396.
__version__ = "2.0.3"

logging.basicConfig(level=logging.INFO)
11 changes: 0 additions & 11 deletions app/enums/sources.py

This file was deleted.

5 changes: 2 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
from fastapi.responses import JSONResponse

from .data import data_source
from .router.v1 import V1
from .router.v2 import V2
from .routers import V1, V2
from .utils.httputils import setup_client_session, teardown_client_session

# ############
Expand Down Expand Up @@ -61,7 +60,7 @@ async def add_datasource(request: Request, call_next):
request.state.source = source

# Move on...
LOGGER.info(f"source provided: {source.__class__.__name__}")
LOGGER.debug(f"source provided: {source.__class__.__name__}")
response = await call_next(request)
return response

Expand Down
39 changes: 37 additions & 2 deletions app/models/location.py → app/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
"""app.models.py"""
from typing import Dict, List

from pydantic import BaseModel

from .latest import Latest
from .timeline import Timelines

class Latest(BaseModel):
"""
Latest model.
"""

confirmed: int
deaths: int
recovered: int


class LatestResponse(BaseModel):
"""
Response for latest.
"""

latest: Latest


class Timeline(BaseModel):
"""
Timeline model.
"""

latest: int
timeline: Dict[str, int] = {}


class Timelines(BaseModel):
"""
Timelines model.
"""

confirmed: Timeline
deaths: Timeline
recovered: Timeline


class Location(BaseModel):
Expand Down
19 changes: 0 additions & 19 deletions app/models/latest.py

This file was deleted.

22 changes: 0 additions & 22 deletions app/models/timeline.py

This file was deleted.

8 changes: 0 additions & 8 deletions app/router/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions app/router/v1/__init__.py

This file was deleted.

20 changes: 0 additions & 20 deletions app/router/v1/all.py

This file was deleted.

11 changes: 0 additions & 11 deletions app/router/v1/confirmed.py

This file was deleted.

11 changes: 0 additions & 11 deletions app/router/v1/deaths.py

This file was deleted.

11 changes: 0 additions & 11 deletions app/router/v1/recovered.py

This file was deleted.

4 changes: 0 additions & 4 deletions app/router/v2/__init__.py

This file was deleted.

21 changes: 0 additions & 21 deletions app/router/v2/latest.py

This file was deleted.

11 changes: 0 additions & 11 deletions app/router/v2/sources.py

This file was deleted.

3 changes: 3 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""app.routers"""
from .v1 import V1
from .v2 import V2
47 changes: 47 additions & 0 deletions app/routers/v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""app.routers.v1.py"""
from fastapi import APIRouter

from ..services.location.jhu import get_category

V1 = APIRouter()


@V1.get("/all")
async def all_categories():
"""Get all the categories."""
confirmed = await get_category("confirmed")
deaths = await get_category("deaths")
recovered = await get_category("recovered")

return {
# Data.
"confirmed": confirmed,
"deaths": deaths,
"recovered": recovered,
# Latest.
"latest": {"confirmed": confirmed["latest"], "deaths": deaths["latest"], "recovered": recovered["latest"],},
}


@V1.get("/confirmed")
async def get_confirmed():
"""Confirmed cases."""
confirmed_data = await get_category("confirmed")

return confirmed_data


@V1.get("/deaths")
async def get_deaths():
"""Total deaths."""
deaths_data = await get_category("deaths")

return deaths_data


@V1.get("/recovered")
async def get_recovered():
"""Recovered cases."""
recovered_data = await get_category("recovered")

return recovered_data
51 changes: 43 additions & 8 deletions app/router/v2/locations.py → app/routers/v2.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
"""app.router.v2.locations.py"""
from fastapi import HTTPException, Request
"""app.routers.v2"""
import enum

from ...enums.sources import Sources
from ...models.location import LocationResponse as Location
from ...models.location import LocationsResponse as Locations
from . import V2
from fastapi import APIRouter, HTTPException, Request

from ..data import DATA_SOURCES
from ..models import LatestResponse, LocationResponse, LocationsResponse

V2 = APIRouter()


class Sources(str, enum.Enum):
"""
A source available for retrieving data.
"""

jhu = "jhu"
csbs = "csbs"
nyt = "nyt"


@V2.get("/latest", response_model=LatestResponse)
async def get_latest(request: Request, source: Sources = "jhu"): # pylint: disable=unused-argument
"""
Getting latest amount of total confirmed cases, deaths, and recoveries.
"""
locations = await request.state.source.get_all()
return {
"latest": {
"confirmed": sum(map(lambda location: location.confirmed, locations)),
"deaths": sum(map(lambda location: location.deaths, locations)),
"recovered": sum(map(lambda location: location.recovered, locations)),
}
}


# pylint: disable=unused-argument,too-many-arguments,redefined-builtin
@V2.get("/locations", response_model=Locations, response_model_exclude_unset=True)
@V2.get("/locations", response_model=LocationsResponse, response_model_exclude_unset=True)
async def get_locations(
request: Request,
source: Sources = "jhu",
Expand Down Expand Up @@ -56,10 +83,18 @@ async def get_locations(


# pylint: disable=invalid-name
@V2.get("/locations/{id}", response_model=Location)
@V2.get("/locations/{id}", response_model=LocationResponse)
async def get_location_by_id(request: Request, id: int, source: Sources = "jhu", timelines: bool = True):
"""
Getting specific location by id.
"""
location = await request.state.source.get(id)
return {"location": location.serialize(timelines)}


@V2.get("/sources")
async def sources():
"""
Retrieves a list of data-sources that are availble to use.
"""
return {"sources": list(DATA_SOURCES.keys())}
11 changes: 6 additions & 5 deletions app/services/location/csbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from ...utils import httputils
from . import LocationService

LOGGER = logging.getLogger("services.location.csbs")


class CSBSLocationService(LocationService):
"""
Expand Down Expand Up @@ -40,15 +42,14 @@ async def get_locations():
:returns: The locations.
:rtype: dict
"""
logger = logging.getLogger("services.location.csbs")
logger.info("Requesting data...")
LOGGER.info("csbs Requesting data...")
async with httputils.CLIENT_SESSION.get(BASE_URL) as response:
text = await response.text()

logger.info("Data received")
LOGGER.info("csbs Data received")

data = list(csv.DictReader(text.splitlines()))
logger.info("CSV parsed")
LOGGER.info("csbs CSV parsed")

locations = []

Expand Down Expand Up @@ -83,7 +84,7 @@ async def get_locations():
int(item["Death"] or 0),
)
)
logger.info("Data normalized")
LOGGER.info("csbs Data normalized")

# Return the locations.
return locations
Loading