Skip to content
Closed
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 app/caches.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ async def load_cache(data_id: str, data, namespace: str = None, cache_life: int
cache = get_cache(namespace)
await cache.set(data_id, data, ttl=cache_life)
LOGGER.info(f"{data_id} cache loaded")
await cache.close()
await cache.close()
4 changes: 3 additions & 1 deletion app/coordinates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""app.coordinates.py"""

from abc import abstractmethod, ABCMeta

class Coordinates:
"""
Expand All @@ -21,3 +21,5 @@ def serialize(self):

def __str__(self):
return "lat: %s, long: %s" % (self.latitude, self.longitude)


10 changes: 7 additions & 3 deletions app/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
from ..services.location.csbs import CSBSLocationService
from ..services.location.jhu import JhuLocationService
from ..services.location.nyt import NYTLocationService
from ..services.location import LocationServiceAbstractionImpl




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


Expand Down
1 change: 1 addition & 0 deletions app/io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""app.io.py"""
from abc import ABCMeta, abstractmethod
import json
import pathlib
from typing import Dict, List, Union
Expand Down
61 changes: 59 additions & 2 deletions app/services/location/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""app.services.location"""
from abc import ABC, abstractmethod

from abc import ABC, abstractmethod, abstractstaticmethod

class LocationService(ABC):
"""
Expand All @@ -26,3 +25,61 @@ async def get(self, id): # pylint: disable=redefined-builtin,invalid-name
:rtype: Location
"""
raise NotImplementedError



'''
This is the interface for the client to interact with in order to handle the calls
'''
class LocationServiceAbstraction(ABC):
"""
Service for retrieving locations.
"""

@abstractmethod
async def get_all(self):
"""
Gets and returns all of the locations.

:returns: The locations.
:rtype: List[Location]
"""
raise NotImplementedError

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

:returns: The location.
:rtype: Location
"""
raise NotImplementedError


'''
This is the implementation that the client will interact with in order to execute the required locationservice implementation
'''
class LocationServiceAbstractionImpl(LocationServiceAbstraction):
def __init__(self, implementation):
self.__provider = implementation

async def get_all(self):
return await self.__provider.get_locations()

async def get(self, id):
return await self.__provider.get(id)


'''
interface for all location services to implement in order to satisfy consistency with each type of locationservice alongside the bridge pattern
'''
class ILocationService(ABC):
@abstractmethod
async def get_locations(self):
return NotImplementedError

@abstractmethod
async def get(self, id):
return NotImplementedError

153 changes: 73 additions & 80 deletions app/services/location/csbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,93 +10,86 @@
from ...coordinates import Coordinates
from ...location.csbs import CSBSLocation
from ...utils import httputils
from . import LocationService
from . import ILocationService

LOGGER = logging.getLogger("services.location.csbs")
# Base URL for fetching data


class CSBSLocationService(LocationService):
class CSBSLocationService(ILocationService):
"""
Service for retrieving locations from csbs
"""

async def get_all(self):
# Get the locations.
locations = await get_locations()
def __init__(self):
self.BASE_URL = "https://facts.csbs.org/covid-19/covid19_county.csv"

@cached(cache=TTLCache(maxsize=1, ttl=1800))
async def get_locations(self):
"""
Retrieves county locations; locations are cached for 1 hour

:returns: The locations.
:rtype: dict
"""
data_id = "csbs.locations"
LOGGER.info(f"{data_id} Requesting data...")
# check shared cache
cache_results = await check_cache(data_id)
if cache_results:
LOGGER.info(f"{data_id} using shared cache results")
locations = cache_results
else:
LOGGER.info(f"{data_id} shared cache empty")
async with httputils.CLIENT_SESSION.get(self.BASE_URL) as response:
text = await response.text()

LOGGER.debug(f"{data_id} Data received")

data = list(csv.DictReader(text.splitlines()))
LOGGER.debug(f"{data_id} CSV parsed")

locations = []

for i, item in enumerate(data):
# General info.
state = item["State Name"]
county = item["County Name"]

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

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

# Append to locations.
locations.append(
CSBSLocation(
# General info.
i,
state,
county,
# Coordinates.
Coordinates(item["Latitude"], item["Longitude"]),
# Last update (parse as ISO).
datetime.strptime(last_update, "%Y-%m-%d %H:%M").isoformat() + "Z",
# Statistics.
int(item["Confirmed"] or 0),
int(item["Death"] or 0),
)
)
LOGGER.info(f"{data_id} Data normalized")
# save the results to distributed cache
# TODO: fix json serialization
try:
await load_cache(data_id, locations)
except TypeError as type_err:
LOGGER.error(type_err)

# Return the locations.
return locations

async def get():
return NotImplementedError

async def get(self, loc_id): # pylint: disable=arguments-differ
# Get location at the index equal to the provided id.
locations = await self.get_all()
return locations[loc_id]


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


@cached(cache=TTLCache(maxsize=1, ttl=1800))
async def get_locations():
"""
Retrieves county locations; locations are cached for 1 hour

:returns: The locations.
:rtype: dict
"""
data_id = "csbs.locations"
LOGGER.info(f"{data_id} Requesting data...")
# check shared cache
cache_results = await check_cache(data_id)
if cache_results:
LOGGER.info(f"{data_id} using shared cache results")
locations = cache_results
else:
LOGGER.info(f"{data_id} shared cache empty")
async with httputils.CLIENT_SESSION.get(BASE_URL) as response:
text = await response.text()

LOGGER.debug(f"{data_id} Data received")

data = list(csv.DictReader(text.splitlines()))
LOGGER.debug(f"{data_id} CSV parsed")

locations = []

for i, item in enumerate(data):
# General info.
state = item["State Name"]
county = item["County Name"]

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

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

# Append to locations.
locations.append(
CSBSLocation(
# General info.
i,
state,
county,
# Coordinates.
Coordinates(item["Latitude"], item["Longitude"]),
# Last update (parse as ISO).
datetime.strptime(last_update, "%Y-%m-%d %H:%M").isoformat() + "Z",
# Statistics.
int(item["Confirmed"] or 0),
int(item["Death"] or 0),
)
)
LOGGER.info(f"{data_id} Data normalized")
# save the results to distributed cache
# TODO: fix json serialization
try:
await load_cache(data_id, locations)
except TypeError as type_err:
LOGGER.error(type_err)

# Return the locations.
return locations
Loading