diff --git a/app/data/__init__.py b/app/data/__init__.py index 60a75dac..76bcf2d7 100644 --- a/app/data/__init__.py +++ b/app/data/__init__.py @@ -3,19 +3,26 @@ from ..services.location.jhu import JhuLocationService from ..services.location.nyt import NYTLocationService -# Mapping of services to data-sources. -DATA_SOURCES = { - "jhu": JhuLocationService(), - "csbs": CSBSLocationService(), - "nyt": NYTLocationService(), -} -def data_source(source): +class Source: + + def __init__(self, source): + # Mapping of services to data-sources. + self.__DATA_SOURCES_LIST = { + "jhu": JhuLocationService(), + "csbs": CSBSLocationService(), + "nyt": NYTLocationService(), + } + + def all_data_source(self): + return self.__DATA_SOURCES_LIST + + def single_data_source(self, source): """ Retrieves the provided data-source service. :returns: The service. :rtype: LocationService """ - return DATA_SOURCES.get(source.lower()) + return self.__DATA_SOURCES_LIST.get(source.lower()) diff --git a/app/location/__init__.py b/app/location/__init__.py index 1da5e9e5..829e73b3 100644 --- a/app/location/__init__.py +++ b/app/location/__init__.py @@ -1,7 +1,7 @@ """app.location""" from ..coordinates import Coordinates from ..utils import countries -from ..utils.populations import country_population +from ..utils.populations import POPULATION # pylint: disable=redefined-builtin,invalid-name @@ -11,6 +11,7 @@ class Location: # pylint: disable=too-many-instance-attributes """ def __init__( + self, id, country, province, coordinates, last_updated, confirmed, deaths, recovered, ): # pylint: disable=too-many-arguments # General info. diff --git a/app/main.py b/app/main.py index b9aff949..5f76dfed 100644 --- a/app/main.py +++ b/app/main.py @@ -14,7 +14,7 @@ from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from .config import get_settings -from .data import data_source +from .data import Source from .routers import V1, V2 from .utils.httputils import setup_client_session, teardown_client_session @@ -46,6 +46,7 @@ ####################### # Scout APM + if SETTINGS.scout_name: # pragma: no cover LOGGER.info(f"Adding Scout APM middleware for `{SETTINGS.scout_name}`") APP.add_middleware(ScoutMiddleware) @@ -67,14 +68,14 @@ ) APP.add_middleware(GZipMiddleware, minimum_size=1000) - +SOURCES = Source() @APP.middleware("http") async def add_datasource(request: Request, call_next): """ Attach the data source to the request.state. """ # Retrieve the datas ource from query param. - source = data_source(request.query_params.get("source", default="jhu")) + source = SOURCE.get_data_source(request.query_params.get("source", default="jhu")) # Abort with 404 if source cannot be found. if not source: diff --git a/app/routers/v2.py b/app/routers/v2.py index 31eb408c..a1405c3a 100644 --- a/app/routers/v2.py +++ b/app/routers/v2.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, HTTPException, Request -from ..data import DATA_SOURCES +from ..data import Source from ..models import LatestResponse, LocationResponse, LocationsResponse V2 = APIRouter() @@ -101,10 +101,10 @@ async def get_location_by_id( location = await request.state.source.get(id) return {"location": location.serialize(timelines)} - +SOURCE = Source() @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(SOURCE.all_data_source)} diff --git a/app/utils/populations.py b/app/utils/populations.py index c02f15a9..d944df25 100644 --- a/app/utils/populations.py +++ b/app/utils/populations.py @@ -10,51 +10,60 @@ GEONAMES_URL = "http://api.geonames.org/countryInfoJSON" GEONAMES_BACKUP_PATH = "geonames_population_mappings.json" -# Fetching of the populations. -def fetch_populations(save=False): - """ - Returns a dictionary containing the population of each country fetched from the GeoNames. - https://www.geonames.org/ - - TODO: only skip writing to the filesystem when deployed with gunicorn, or handle concurent access, or use DB. - - :returns: The mapping of populations. - :rtype: dict - """ - LOGGER.info("Fetching populations...") - - # Mapping of populations - mappings = {} - - # Fetch the countries. - try: - countries = requests.get(GEONAMES_URL, params={"username": "dperic"}, timeout=1.25).json()[ - "geonames" - ] - # Go through all the countries and perform the mapping. - for country in countries: - mappings.update({country["countryCode"]: int(country["population"]) or None}) - - if mappings and save: - LOGGER.info(f"Saving population data to {app.io.save(GEONAMES_BACKUP_PATH, mappings)}") - except (json.JSONDecodeError, KeyError, requests.exceptions.Timeout) as err: - LOGGER.warning(f"Error pulling population data. {err.__class__.__name__}: {err}") - mappings = app.io.load(GEONAMES_BACKUP_PATH) - LOGGER.info(f"Using backup data from {GEONAMES_BACKUP_PATH}") - # Finally, return the mappings. - LOGGER.info("Fetched populations") - return mappings - - -# Mapping of alpha-2 codes country codes to population. -POPULATIONS = fetch_populations() - -# Retrieving. -def country_population(country_code, default=None): - """ - Fetches the population of the country with the provided country code. - - :returns: The population. - :rtype: int - """ - return POPULATIONS.get(country_code, default) + +class POPULATION: + # Fetching of the populations. + POPULATION = fetch_population() + + def __init__(self): + self.population = POPULATION + + def fetch_populations(save=False): + """ + Returns a dictionary containing the population of each country fetched from the GeoNames. + https://www.geonames.org/ + + TODO: only skip writing to the filesystem when deployed with gunicorn, or handle concurent access, or use DB. + + :returns: The mapping of populations. + :rtype: dict + """ + LOGGER.info("Fetching populations...") + + # Mapping of populations + mappings = {} + + # Fetch the countries. + try: + countries = requests.get(GEONAMES_URL, params={"username": "dperic"}, timeout=1.25).json()[ + "geonames" + ] + # Go through all the countries and perform the mapping. + for country in countries: + mappings.update({country["countryCode"]: int(country["population"]) or None}) + + if mappings and save: + LOGGER.info(f"Saving population data to {app.io.save(GEONAMES_BACKUP_PATH, mappings)}") + except (json.JSONDecodeError, KeyError, requests.exceptions.Timeout) as err: + LOGGER.warning(f"Error pulling population data. {err.__class__.__name__}: {err}") + mappings = app.io.load(GEONAMES_BACKUP_PATH) + LOGGER.info(f"Using backup data from {GEONAMES_BACKUP_PATH}") + # Finally, return the mappings. + LOGGER.info("Fetched populations") + return mappings + + + # Mapping of alpha-2 codes country codes to population. + POPULATIONS = fetch_populations() + + # Retrieving. + def get_country_population(country_code, default=None): + """ + Fetches the population of the country with the provided country code. + + :returns: The population. + :rtype: int + """ + return self.population.get(country_code, default) + +