diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ecdd0b6..91b53c02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,10 @@ Please write new test cases for new code you create. * We will love you forever if you include unit tests. We can always use more test coverage * If you have updated [Pipefile](./Pipfile), you have to update `Pipfile.lock`, `requirements.txt` and `requirements-dev.txt`. See section [Update requirements files](./README.md#update-requirements-files). +## Some about the database + +* There is gonna be something that must be added into the database because blah blah this is a test. This is something in consideration about the database wanting to be added to add stability blah + ## Your First Code Contribution Unsure where to begin contributing to coronavirus-tracker-api ? You can start by looking through these issues labels: diff --git a/README.md b/README.md index 7355d0af..84ce0d6c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Provides up-to-date data about Coronavirus outbreak. Includes numbers about confirmed cases, deaths and recovered. Support multiple data-sources. +Test + ![Travis build](https://api.travis-ci.com/ExpDev07/coronavirus-tracker-api.svg?branch=master) [![Coverage Status](https://coveralls.io/repos/github/ExpDev07/coronavirus-tracker-api/badge.svg?branch=master)](https://coveralls.io/github/ExpDev07/coronavirus-tracker-api?branch=master) [![License](https://img.shields.io/github/license/ExpDev07/coronavirus-tracker-api)](LICENSE.md) diff --git a/app/data/__init__.py b/app/data/__init__.py index 60a75dac..8a04dbd8 100644 --- a/app/data/__init__.py +++ b/app/data/__init__.py @@ -1,21 +1,38 @@ """app.data""" -from ..services.location.csbs import CSBSLocationService -from ..services.location.jhu import JhuLocationService -from ..services.location.nyt import NYTLocationService +from ..services.location.csbs import CSBSLocationServicee +from ..services.location.jhu import JhuLocationServicee +from ..services.location.nyt import NYTLocationServicee +from app.services.location.__init__ import LocationServicer -# Mapping of services to data-sources. -DATA_SOURCES = { - "jhu": JhuLocationService(), - "csbs": CSBSLocationService(), - "nyt": NYTLocationService(), -} +class DataSourceSingletonMeta(type): + """ + access point to the DataSource + """ + _instances = {} -def data_source(source): - """ - Retrieves the provided data-source service. + def __call__(cls, *args, **kwargs): + """ + Possible changes to the value of the `__init__` argument do not affect + the returned instance. + """ + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] - :returns: The service. - :rtype: LocationService - """ - return DATA_SOURCES.get(source.lower()) + +class DataSourceSingleton(metaclass=DataSourceSingletonMeta): + DATA_SOURCES = { + "jhu": LocationServicer(JhuLocationServicee()), + "csbs": LocationServicer(CSBSLocationServicee()), + "nyt": LocationServicer(NYTLocationServicee()), + } + def get_data_source(self, dataSource): + return self.DATA_SOURCES.get(dataSource.lower()) + ... + + def get_data_source_list(self): + return self.DATA_SOURCES + + diff --git a/app/io.py b/app/io.py index 2a563b15..9bf0ba50 100644 --- a/app/io.py +++ b/app/io.py @@ -2,36 +2,103 @@ import json import pathlib from typing import Dict, List, Union +from abc import ABC, abstractmethod import aiofiles -HERE = pathlib.Path(__file__) -DATA = HERE.joinpath("..", "data").resolve() - - -def save( - name: str, - content: Union[str, Dict, List], - write_mode: str = "w", - indent: int = 2, - **json_dumps_kwargs, -) -> pathlib.Path: - """Save content to a file. If content is a dictionary, use json.dumps().""" - path = DATA / name - if isinstance(content, (dict, list)): - content = json.dumps(content, indent=indent, **json_dumps_kwargs) - with open(DATA / name, mode=write_mode) as f_out: - f_out.write(content) - return path - - -def load(name: str, **json_kwargs) -> Union[str, Dict, List]: - """Loads content from a file. If file ends with '.json', call json.load() and return a Dictionary.""" - path = DATA / name - with open(path) as f_in: - if path.suffix == ".json": - return json.load(f_in, **json_kwargs) - return f_in.read() +from __future__ import annotations + + + +class Context: + + _state = None + + + def __init__(self, state): + self.transition_to(state) + + def transition_to(self, state: State): + + self._state = state + self._state.context = self + + + + def request_save(self): + self._state.handle_save() + + def request_load(self): + self._state.handle_load() + + +class State(ABC): + HERE = pathlib.Path(__file__) + DATA = HERE.joinpath("..", "data").resolve() + @property + def context(self): + return self._context + + @context.setter + def context(self, context: Context): + self._context = context + + @abstractmethod + def handle_save(self) -> None: + pass + + @abstractmethod + def handle_load(self) -> None: + pass + + +class SaveState(State): + def handle_save(self, name: str, + content: Union[str, Dict, List], + write_mode: str = "w", + indent: int = 2, + **json_dumps_kwargs,): + """Save content to a file. If content is a dictionary, use json.dumps().""" + path = self.DATA / name + if isinstance(content, (dict, list)): + content = json.dumps(content, indent=indent, **json_dumps_kwargs) + with open(self.DATA / name, mode=write_mode) as f_out: + f_out.write(content) + return path + +class LoadState(State): + def handle_load(name: str, **json_kwargs): + path = DATA / name + with open(path) as f_in: + if path.suffix == ".json": + return json.load(f_in, **json_kwargs) + return f_in.read() + + + +# def save( +# name: str, +# content: Union[str, Dict, List], +# write_mode: str = "w", +# indent: int = 2, +# **json_dumps_kwargs, +# ) -> pathlib.Path: +# """Save content to a file. If content is a dictionary, use json.dumps().""" +# path = DATA / name +# if isinstance(content, (dict, list)): +# content = json.dumps(content, indent=indent, **json_dumps_kwargs) +# with open(DATA / name, mode=write_mode) as f_out: +# f_out.write(content) +# return path + + +# def load(name: str, **json_kwargs) -> Union[str, Dict, List]: +# """Loads content from a file. If file ends with '.json', call json.load() and return a Dictionary.""" +# path = DATA / name +# with open(path) as f_in: +# if path.suffix == ".json": +# return json.load(f_in, **json_kwargs) +# return f_in.read() class AIO: diff --git a/app/main.py b/app/main.py index b9aff949..0f6ee03a 100644 --- a/app/main.py +++ b/app/main.py @@ -14,7 +14,8 @@ from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from .config import get_settings -from .data import data_source +# from .data import data_source +from .data import DataSourceSingleton from .routers import V1, V2 from .utils.httputils import setup_client_session, teardown_client_session @@ -73,8 +74,12 @@ 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 = DataSourceSingleton().get_data_source(request.query_params.get("source", default="jhu")) + # 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..0961d1bc 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 DataSourceSingleton from ..models import LatestResponse, LocationResponse, LocationsResponse V2 = APIRouter() @@ -107,4 +107,4 @@ async def sources(): """ Retrieves a list of data-sources that are availble to use. """ - return {"sources": list(DATA_SOURCES.keys())} + return {"sources": list(DataSourceSingleton().get_data_source_list().keys())} diff --git a/app/services/location/__init__.py b/app/services/location/__init__.py index 6d292b54..b22fb02b 100644 --- a/app/services/location/__init__.py +++ b/app/services/location/__init__.py @@ -2,27 +2,34 @@ from abc import ABC, abstractmethod -class LocationService(ABC): +class LocationServicer: # The 'Remote' of the Pattern """ - Service for retrieving locations. + Abstraction class handling the 'Front End' portion of the project """ - @abstractmethod - async def get_all(self): - """ - Gets and returns all of the locations. + def __init__(self, implementation): + self.implementation = implementation + + @abstractmethod + def all_locations(self): + return self.implementation.get_all() + + @abstractmethod + def locations(self): + return self.implementation.get() + +class LocationServicee: # The 'Device' with the implmentation + """ + Implementation Interface + """ + + def get_all(self): + # No implementation - interface method + pass + + def get(self): + # No implementation - interface method + pass + - :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 diff --git a/app/services/location/csbs.py b/app/services/location/csbs.py index 444ebad6..f931e95e 100644 --- a/app/services/location/csbs.py +++ b/app/services/location/csbs.py @@ -10,12 +10,12 @@ from ...coordinates import Coordinates from ...location.csbs import CSBSLocation from ...utils import httputils -from . import LocationService +from . import LocationServicee LOGGER = logging.getLogger("services.location.csbs") -class CSBSLocationService(LocationService): +class CSBSLocationServicee(LocationServicee): """ Service for retrieving locations from csbs """ @@ -30,7 +30,6 @@ async def get(self, loc_id): # pylint: disable=arguments-differ 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" diff --git a/app/services/location/jhu.py b/app/services/location/jhu.py index ebed3960..75ee37fd 100644 --- a/app/services/location/jhu.py +++ b/app/services/location/jhu.py @@ -15,13 +15,13 @@ from ...utils import countries from ...utils import date as date_util from ...utils import httputils -from . import LocationService +from . import LocationServicee LOGGER = logging.getLogger("services.location.jhu") PID = os.getpid() -class JhuLocationService(LocationService): +class JhuLocationServicee(LocationServicee): """ Service for retrieving locations from Johns Hopkins CSSE (https://github.com/CSSEGISandData/COVID-19). """ diff --git a/app/services/location/nyt.py b/app/services/location/nyt.py index 1f25ec34..5d4874a1 100644 --- a/app/services/location/nyt.py +++ b/app/services/location/nyt.py @@ -11,12 +11,12 @@ from ...location.nyt import NYTLocation from ...models import Timeline from ...utils import httputils -from . import LocationService +from . import LocationServicee LOGGER = logging.getLogger("services.location.nyt") -class NYTLocationService(LocationService): +class NYTLocationServicee(LocationServicee): """ Service for retrieving locations from New York Times (https://github.com/nytimes/covid-19-data). """