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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ TEST = tests
test:
pytest -v $(TEST) --cov-report term --cov-report xml --cov=$(APP)
lint:
pylint $(APP) || true
pylint $(APP)

fmt:
invoke fmt
Expand Down
7 changes: 6 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
"""
Corona Virus Tracker API
~~~~~~~~~~~~~~~~~~~~~~~~
API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak.
"""
# See PEP396.
__version__ = "2.0"
__version__ = "2.0.1"
7 changes: 3 additions & 4 deletions app/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"""app.config.settings.py"""
import os

# Load enviroment variables from .env file.
from dotenv import load_dotenv

load_dotenv()

"""
The port to serve the app application on.
"""
PORT = int(os.getenv("PORT", 5000))
# The port to serve the app application on.
PORT = int(os.getenv("PORT", "5000"))
3 changes: 3 additions & 0 deletions app/coordinates.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""app.coordinates.py"""


class Coordinates:
"""
A position on earth using decimal coordinates (latitude and longitude).
Expand Down
5 changes: 3 additions & 2 deletions app/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""app.data"""
from ..services.location.csbs import CSBSLocationService
from ..services.location.jhu import JhuLocationService

# Mapping of services to data-sources.
data_sources = {"jhu": JhuLocationService(), "csbs": CSBSLocationService()}
DATA_SOURCES = {"jhu": JhuLocationService(), "csbs": CSBSLocationService()}


def data_source(source):
Expand All @@ -12,4 +13,4 @@ def data_source(source):
:returns: The service.
:rtype: LocationService
"""
return data_sources.get(source.lower())
return DATA_SOURCES.get(source.lower())
12 changes: 9 additions & 3 deletions app/location/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"""app.location"""
from ..coordinates import Coordinates
from ..utils import countries
from ..utils.populations import country_population


class Location:
# pylint: disable=redefined-builtin,invalid-name
class Location: # pylint: disable=too-many-instance-attributes
"""
A location in the world affected by the coronavirus.
"""

def __init__(self, id, country, province, coordinates, last_updated, confirmed, deaths, recovered):
def __init__(
self, id, country, province, coordinates, last_updated, confirmed, deaths, recovered
): # pylint: disable=too-many-arguments
# General info.
self.id = id
self.country = country.strip()
Expand All @@ -31,7 +35,7 @@ def country_code(self):
:returns: The country code.
:rtype: str
"""
return (countries.country_code(self.country) or countries.default_country_code).upper()
return (countries.country_code(self.country) or countries.DEFAULT_COUNTRY_CODE).upper()

@property
def country_population(self):
Expand Down Expand Up @@ -71,6 +75,7 @@ class TimelinedLocation(Location):
A location with timelines.
"""

# pylint: disable=too-many-arguments
def __init__(self, id, country, province, coordinates, last_updated, timelines):
super().__init__(
# General info.
Expand All @@ -88,6 +93,7 @@ def __init__(self, id, country, province, coordinates, last_updated, timelines):
# Set timelines.
self.timelines = timelines

# pylint: disable=arguments-differ
def serialize(self, timelines=False):
"""
Serializes the location into a dict.
Expand Down
4 changes: 3 additions & 1 deletion app/location/csbs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""app.locations.csbs.py"""
from . import Location


Expand All @@ -6,6 +7,7 @@ class CSBSLocation(Location):
A CSBS (county) location.
"""

# pylint: disable=too-many-arguments,redefined-builtin
def __init__(self, id, state, county, coordinates, last_updated, confirmed, deaths):
super().__init__(
# General info.
Expand All @@ -23,7 +25,7 @@ def __init__(self, id, state, county, coordinates, last_updated, confirmed, deat
self.state = state
self.county = county

def serialize(self, timelines=False):
def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument
"""
Serializes the location into a dict.

Expand Down
21 changes: 13 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from fastapi.responses import JSONResponse

from .data import data_source
from .router.v1 import router as v1router
from .router.v2 import router as v2router
from .router.v1 import V1
from .router.v2 import V2

# ############
# FastAPI App
Expand All @@ -21,7 +21,10 @@

APP = FastAPI(
title="Coronavirus Tracker",
description="API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak. Project page: https://github.com/ExpDev07/coronavirus-tracker-api.",
description=(
"API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak."
" Project page: https://github.com/ExpDev07/coronavirus-tracker-api."
),
version="2.0.1",
docs_url="/",
redoc_url="/docs",
Expand All @@ -36,7 +39,7 @@
CORSMiddleware, allow_credentials=True, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"],
)

# TODO this could probably just be a FastAPI dependency.

@APP.middleware("http")
async def add_datasource(request: Request, call_next):
"""
Expand Down Expand Up @@ -64,7 +67,9 @@ async def add_datasource(request: Request, call_next):


@APP.exception_handler(pydantic.error_wrappers.ValidationError)
async def handle_validation_error(request: Request, exc: pydantic.error_wrappers.ValidationError):
async def handle_validation_error(
request: Request, exc: pydantic.error_wrappers.ValidationError
): # pylint: disable=unused-argument
"""
Handles validation errors.
"""
Expand All @@ -77,12 +82,12 @@ async def handle_validation_error(request: Request, exc: pydantic.error_wrappers


# Include routers.
APP.include_router(v1router, prefix="", tags=["v1"])
APP.include_router(v2router, prefix="/v2", tags=["v2"])
APP.include_router(V1, prefix="", tags=["v1"])
APP.include_router(V2, prefix="/v2", tags=["v2"])


# Running of app.
if __name__ == "__main__":
uvicorn.run(
"app.main:APP", host="127.0.0.1", port=int(os.getenv("PORT", 5000)), log_level="info",
"app.main:APP", host="127.0.0.1", port=int(os.getenv("PORT", "5000")), log_level="info",
)
2 changes: 2 additions & 0 deletions app/router/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""app.router"""
from fastapi import APIRouter

# pylint: disable=redefined-builtin
from .v1 import all, confirmed, deaths, recovered

# The routes.
Expand Down
3 changes: 2 additions & 1 deletion app/router/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""app.router.v1"""
from fastapi import APIRouter

router = APIRouter()
V1 = APIRouter()
9 changes: 5 additions & 4 deletions app/router/v1/all.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""app.router.v1.all.py"""
from ...services.location.jhu import get_category
from . import router
from . import V1


@router.get("/all")
def all():
# Get all the categories.
@V1.get("/all")
def all(): # pylint: disable=redefined-builtin
"""Get all the categories."""
confirmed = get_category("confirmed")
deaths = get_category("deaths")
recovered = get_category("recovered")
Expand Down
10 changes: 5 additions & 5 deletions app/router/v1/confirmed.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""app.router.v1.confirmed.py"""
from ...services.location.jhu import get_category
from . import router
from . import V1


@router.get("/confirmed")
@V1.get("/confirmed")
def confirmed():
confirmed = get_category("confirmed")

return confirmed
"""Confirmed cases."""
return get_category("confirmed")
10 changes: 5 additions & 5 deletions app/router/v1/deaths.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""app.router.v1.deaths.py"""
from ...services.location.jhu import get_category
from . import router
from . import V1


@router.get("/deaths")
@V1.get("/deaths")
def deaths():
deaths = get_category("deaths")

return deaths
"""Total deaths."""
return get_category("deaths")
10 changes: 5 additions & 5 deletions app/router/v1/recovered.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""app.router.v1.recovered.py"""
from ...services.location.jhu import get_category
from . import router
from . import V1


@router.get("/recovered")
@V1.get("/recovered")
def recovered():
recovered = get_category("recovered")

return recovered
"""Recovered cases."""
return get_category("recovered")
3 changes: 2 additions & 1 deletion app/router/v2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""app.router.v2"""
from fastapi import APIRouter

router = APIRouter()
V2 = APIRouter()
7 changes: 4 additions & 3 deletions app/router/v2/latest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""app.router.v2.latest.py"""
from fastapi import Request

from ...enums.sources import Sources
from ...models.latest import LatestResponse as Latest
from . import router
from . import V2


@router.get("/latest", response_model=Latest)
def get_latest(request: Request, source: Sources = "jhu"):
@V2.get("/latest", response_model=Latest)
def get_latest(request: Request, source: Sources = "jhu"): # pylint: disable=unused-argument
"""
Getting latest amount of total confirmed cases, deaths, and recoveries.
"""
Expand Down
9 changes: 6 additions & 3 deletions app/router/v2/locations.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""app.router.v2.locations.py"""
from fastapi import HTTPException, Request

from ...enums.sources import Sources
from ...models.location import LocationResponse as Location
from ...models.location import LocationsResponse as Locations
from . import router
from . import V2


@router.get("/locations", response_model=Locations, response_model_exclude_unset=True)
# pylint: disable=unused-argument,too-many-arguments,redefined-builtin
@V2.get("/locations", response_model=Locations, response_model_exclude_unset=True)
def get_locations(
request: Request,
source: Sources = "jhu",
Expand Down Expand Up @@ -53,7 +55,8 @@ def get_locations(
}


@router.get("/locations/{id}", response_model=Location)
# pylint: disable=invalid-name
@V2.get("/locations/{id}", response_model=Location)
def get_location_by_id(request: Request, id: int, source: Sources = "jhu", timelines: bool = True):
"""
Getting specific location by id.
Expand Down
9 changes: 5 additions & 4 deletions app/router/v2/sources.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from ...data import data_sources
from . import router
"""app.router.v2.sources.py"""
from ...data import DATA_SOURCES
from . import V2


@router.get("/sources")
@V2.get("/sources")
async def sources():
"""
Retrieves a list of data-sources that are availble to use.
"""
return {"sources": list(data_sources.keys())}
return {"sources": list(DATA_SOURCES.keys())}
3 changes: 2 additions & 1 deletion app/services/location/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""app.services.location"""
from abc import ABC, abstractmethod


Expand All @@ -17,7 +18,7 @@ def get_all(self):
raise NotImplementedError

@abstractmethod
def get(self, id):
def get(self, id): # pylint: disable=redefined-builtin,invalid-name
"""
Gets and returns location with the provided id.

Expand Down
13 changes: 7 additions & 6 deletions app/services/location/csbs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""app.services.location.csbs.py"""
import csv
from datetime import datetime

Expand All @@ -18,12 +19,12 @@ def get_all(self):
# Get the locations
return get_locations()

def get(self, id):
return self.get_all()[id]
def get(self, loc_id): # pylint: disable=arguments-differ
return self.get_all()[loc_id]


# Base URL for fetching data
base_url = "https://facts.csbs.org/covid-19/covid19_county.csv"
BASE_URL = "https://facts.csbs.org/covid-19/covid19_county.csv"


@cached(cache=TTLCache(maxsize=1, ttl=3600))
Expand All @@ -34,7 +35,7 @@ def get_locations():
:returns: The locations.
:rtype: dict
"""
request = requests.get(base_url)
request = requests.get(BASE_URL)
text = request.text

data = list(csv.DictReader(text.splitlines()))
Expand All @@ -47,11 +48,11 @@ def get_locations():
county = item["County Name"]

# Ensure country is specified.
if county == "Unassigned" or county == "Unknown":
if county in {"Unassigned", "Unknown"}:
continue

# Coordinates.
coordinates = Coordinates(item["Latitude"], item["Longitude"])
coordinates = Coordinates(item["Latitude"], item["Longitude"]) # pylint: disable=unused-variable

# Date string without "EDT" at end.
last_update = " ".join(item["Last Update"].split(" ")[0:2])
Expand Down
Loading