From 8863e2a10196f3d654ac3c83de93fcac0a41eae0 Mon Sep 17 00:00:00 2001 From: snwchow <85044004+snwchow@users.noreply.github.com> Date: Sun, 15 Aug 2021 12:44:24 -0400 Subject: [PATCH] Add files via upload (An attempt of) Implementing the Builder Design Pattern, generating csbs or nyt object using the BDP so that in the case where a new function is added in the future which doesn't require ALL the individual parts, the Builder can "cherry pick" the parts it needs and create a customized object for the new function to use, allowing greater flexibility. This could potentially reduce the number of parameters in the constructors to create the said customized csbs/nyt object, which would provide better readability, easier to read/understand what parameters the customized object has. Note that this (attempt of) implementation is meant to support the original functions, and that the implementation having LOTS of duplicated code (from csbs.py and nyt.py) suggests that the current implementation can be further refactored so that "code smell" can occur less. *regarding proper implementation* Location class and TimelinedLocation class in __init__.py should be removed as it is replaced by LocationBuilderInterface and TimelinedLocationBuilderInterface respectively as the Abstract interface. CSBSLocation class in csbs.py should be removed as it is replaced by CSBSLocationBuilder as the concrete Builder. similarly, NYTLocation with NYTLocationBuilder in nyt.py The Director and Product current located in csbs.py and nyt.py should be in their own respective module/file so that it is better organized and structured. (but it still works right now) The client code at the end of csbs.py and nyt.py should belong to another module/file where the other client codes are. The pseudo code of implementation have some lines of code commented out, which should reflect how it should looks like when properly implemented; Those lines of code are currently commented out to prevent program from unable to compile. --- app/location/__init__.py | 379 ++++++++++++++++++++++++++------------- app/location/csbs.py | 163 ++++++++++++----- app/location/nyt.py | 130 ++++++++++---- 3 files changed, 473 insertions(+), 199 deletions(-) diff --git a/app/location/__init__.py b/app/location/__init__.py index 1da5e9e5..da399738 100644 --- a/app/location/__init__.py +++ b/app/location/__init__.py @@ -1,124 +1,255 @@ -"""app.location""" -from ..coordinates import Coordinates -from ..utils import countries -from ..utils.populations import country_population - - -# 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, - ): # pylint: disable=too-many-arguments - # General info. - self.id = id - self.country = country.strip() - self.province = province.strip() - self.coordinates = coordinates - - # Last update. - self.last_updated = last_updated - - # Statistics. - self.confirmed = confirmed - self.deaths = deaths - self.recovered = recovered - - @property - def country_code(self): - """ - Gets the alpha-2 code represention of the country. Returns 'XX' if none is found. - - :returns: The country code. - :rtype: str - """ - return (countries.country_code(self.country) or countries.DEFAULT_COUNTRY_CODE).upper() - - @property - def country_population(self): - """ - Gets the population of this location. - - :returns: The population. - :rtype: int - """ - return country_population(self.country_code) - - def serialize(self): - """ - Serializes the location into a dict. - - :returns: The serialized location. - :rtype: dict - """ - return { - # General info. - "id": self.id, - "country": self.country, - "country_code": self.country_code, - "country_population": self.country_population, - "province": self.province, - # Coordinates. - "coordinates": self.coordinates.serialize(), - # Last updated. - "last_updated": self.last_updated, - # Latest data (statistics). - "latest": { - "confirmed": self.confirmed, - "deaths": self.deaths, - "recovered": self.recovered, - }, - } - - -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. - id, - country, - province, - coordinates, - last_updated, - # Statistics (retrieve latest from timelines). - confirmed=timelines.get("confirmed").latest or 0, - deaths=timelines.get("deaths").latest or 0, - recovered=timelines.get("recovered").latest or 0, - ) - - # Set timelines. - self.timelines = timelines - - # pylint: disable=arguments-differ - def serialize(self, timelines=False): - """ - Serializes the location into a dict. - - :param timelines: Whether to include the timelines. - :returns: The serialized location. - :rtype: dict - """ - serialized = super().serialize() - - # Whether to include the timelines or not. - if timelines: - serialized.update( - { - "timelines": { - # Serialize all the timelines. - key: value.serialize() - for (key, value) in self.timelines.items() - } - } - ) - - # Return the serialized location. - return serialized +"""app.location""" +from ..coordinates import Coordinates +from ..utils import countries +from ..utils.populations import country_population +from abc import ABCMeta + +# 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, + ): # pylint: disable=too-many-arguments + # General info. + self.id = id + self.country = country.strip() + self.province = province.strip() + self.coordinates = coordinates + + # Last update. + self.last_updated = last_updated + + # Statistics. + self.confirmed = confirmed + self.deaths = deaths + self.recovered = recovered + + @property + def country_code(self): + """ + Gets the alpha-2 code represention of the country. Returns 'XX' if none is found. + :returns: The country code. + :rtype: str + """ + return (countries.country_code(self.country) or countries.DEFAULT_COUNTRY_CODE).upper() + + @property + def country_population(self): + """ + Gets the population of this location. + :returns: The population. + :rtype: int + """ + return country_population(self.country_code) + + def serialize(self): + """ + Serializes the location into a dict. + :returns: The serialized location. + :rtype: dict + """ + return { + # General info. + "id": self.id, + "country": self.country, + "country_code": self.country_code, + "country_population": self.country_population, + "province": self.province, + # Coordinates. + "coordinates": self.coordinates.serialize(), + # Last updated. + "last_updated": self.last_updated, + # Latest data (statistics). + "latest": { + "confirmed": self.confirmed, + "deaths": self.deaths, + "recovered": self.recovered, + }, + } + + +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. + id, + country, + province, + coordinates, + last_updated, + # Statistics (retrieve latest from timelines). + confirmed=timelines.get("confirmed").latest or 0, + deaths=timelines.get("deaths").latest or 0, + recovered=timelines.get("recovered").latest or 0, + ) + + # Set timelines. + self.timelines = timelines + + # pylint: disable=arguments-differ + def serialize(self, timelines=False): + """ + Serializes the location into a dict. + :param timelines: Whether to include the timelines. + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize() + + # Whether to include the timelines or not. + if timelines: + serialized.update( + { + "timelines": { + # Serialize all the timelines. + key: value.serialize() + for (key, value) in self.timelines.items() + } + } + ) + + # Return the serialized location. + return serialized + + +class LocationBuilderInterface(metaclass=ABCMeta): # pylint: disable=too-many-instance-attributes + """ + The Abstract Location Builder Interface + """ + + 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() + self.province = province.strip() + self.coordinates = coordinates + + # Last update. + self.last_updated = last_updated + + # Statistics. + self.confirmed = confirmed + self.deaths = deaths + self.recovered = recovered + + @property + def country_code(self): + """ + Gets the alpha-2 code represention of the country. Returns 'XX' if none is found. + :returns: The country code. + :rtype: str + """ + return (countries.country_code(self.country) or countries.DEFAULT_COUNTRY_CODE).upper() + + @property + def country_population(self): + """ + Gets the population of this location. + :returns: The population. + :rtype: int + """ + return country_population(self.country_code) + + def serialize(self): + """ + Serializes the location into a dict. + :returns: The serialized location. + :rtype: dict + """ + return { + # General info. + "id": self.id, + "country": self.country, + "country_code": self.country_code, + "country_population": self.country_population, + "province": self.province, + # Coordinates. + "coordinates": self.coordinates.serialize(), + # Last updated. + "last_updated": self.last_updated, + # Latest data (statistics). + "latest": { + "confirmed": self.confirmed, + "deaths": self.deaths, + "recovered": self.recovered, + }, + } + + # def build_part_a(): + # "Build part a, such as id" + # + # def build_part_b(): + # "Build part b, such as state" + # ... + # def get_result(): + # "Return final product" + + +class TimelinedLocationBuilderInterface(LocationBuilderInterface): + """ + The Abstract Timelined Location Builder Interface + """ + + # pylint: disable=too-many-arguments + def __init__(self, id, country, province, coordinates, last_updated, timelines): + super().__init__( + # General info. + id, + country, + province, + coordinates, + last_updated, + # Statistics (retrieve latest from timelines). + confirmed=timelines.get("confirmed").latest or 0, + deaths=timelines.get("deaths").latest or 0, + recovered=timelines.get("recovered").latest or 0, + ) + + # Set timelines. + self.timelines = timelines + + # pylint: disable=arguments-differ + def serialize(self, timelines=False): + """ + Serializes the location into a dict. + :param timelines: Whether to include the timelines. + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize() + + # Whether to include the timelines or not. + if timelines: + serialized.update( + { + "timelines": { + # Serialize all the timelines. + key: value.serialize() + for (key, value) in self.timelines.items() + } + } + ) + + # Return the serialized location. + return serialized + + # def build_part_a(): + # "Build part a, such as id" + # + # def build_part_b(): + # "Build part b, such as state" + # ... + # def get_result(): + # "Return final product" \ No newline at end of file diff --git a/app/location/csbs.py b/app/location/csbs.py index 649e8b22..61c6320d 100644 --- a/app/location/csbs.py +++ b/app/location/csbs.py @@ -1,43 +1,120 @@ -"""app.locations.csbs.py""" -from . import Location - - -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. - id, - "US", - state, - coordinates, - last_updated, - # Statistics. - confirmed=confirmed, - deaths=deaths, - recovered=0, - ) - - self.state = state - self.county = county - - def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument - """ - Serializes the location into a dict. - - :returns: The serialized location. - :rtype: dict - """ - serialized = super().serialize() - - # Update with new fields. - serialized.update( - {"state": self.state, "county": self.county,} - ) - - # Return the serialized location. - return serialized +"""app.locations.csbs.py""" +from . import Location +from . import LocationBuilderInterface + + +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. + id, + "US", + state, + coordinates, + last_updated, + # Statistics. + confirmed=confirmed, + deaths=deaths, + recovered=0, + ) + + self.state = state + self.county = county + + def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument + """ + Serializes the location into a dict. + + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize() + + # Update with new fields. + serialized.update( + {"state": self.state, "county": self.county, } + ) + + # Return the serialized location. + return serialized + + +class CSBSLocationBuilder(LocationBuilderInterface): + """" + The Concrete Builder for CSBS + """ + + # pylint: disable=too-many-arguments,redefined-builtin + def __init__(self, id, state, county, coordinates, last_updated, confirmed, deaths): + super().__init__( + # General info. + id, + "US", + state, + coordinates, + last_updated, + # Statistics. + confirmed=confirmed, + deaths=deaths, + recovered=0, + ) + + self.state = state + self.county = county + self.product = CSBSProduct() + + def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument + """ + Serializes the location into a dict. + + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize() + + # Update with new fields. + serialized.update( + {"state": self.state, "county": self.county, } + ) + + # Return the serialized location. + return serialized + + #def build_part_a(self): + # #this should actually puts the "part" inside the object, part being id, state, country, etc. + # self.product.parts.append('additional parts') + #return self + +class CSBSDirector: + """ + The Director, building a complex representation. + """ + def construct(): + return CSBSLocationBuilder()#\ + # # build and add part_a, ie. id; part_b, ie. state; etc. + # .build_part_a() + + +class CSBSProduct(): + """ + The Product + """ + def __init__(self): + """ + the CSBS product should be empty when the init method is first called + containing a parts attribute + which is of type dictionary + """ + self.parts = {} + pass + + +# # The Client +# #client code that initiates the construction process +# #this should belong where all the other client codes are +# PRODUCT = CSBSDirector.construct() \ No newline at end of file diff --git a/app/location/nyt.py b/app/location/nyt.py index ad92212e..7bf62a19 100644 --- a/app/location/nyt.py +++ b/app/location/nyt.py @@ -1,32 +1,98 @@ -"""app.locations.nyt.py""" -from . import TimelinedLocation - - -class NYTLocation(TimelinedLocation): - """ - A NYT (county) Timelinedlocation. - """ - - # pylint: disable=too-many-arguments,redefined-builtin - def __init__(self, id, state, county, coordinates, last_updated, timelines): - super().__init__(id, "US", state, coordinates, last_updated, timelines) - - self.state = state - self.county = county - - def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument - """ - Serializes the location into a dict. - - :returns: The serialized location. - :rtype: dict - """ - serialized = super().serialize(timelines) - - # Update with new fields. - serialized.update( - {"state": self.state, "county": self.county,} - ) - - # Return the serialized location. - return serialized +"""app.locations.nyt.py""" +from . import TimelinedLocation +from . import TimelinedLocationBuilderInterface + + +class NYTLocation(TimelinedLocation): + """ + A NYT (county) Timelinedlocation. + """ + + # pylint: disable=too-many-arguments,redefined-builtin + def __init__(self, id, state, county, coordinates, last_updated, timelines): + super().__init__(id, "US", state, coordinates, last_updated, timelines) + + self.state = state + self.county = county + + def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument + """ + Serializes the location into a dict. + + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize(timelines) + + # Update with new fields. + serialized.update( + {"state": self.state, "county": self.county,} + ) + + # Return the serialized location. + return serialized + + +class NYTLocationBuilder(TimelinedLocationBuilderInterface): + """ + The Concrete Builder for NYT + """ + + # pylint: disable=too-many-arguments,redefined-builtin + def __init__(self, id, state, county, coordinates, last_updated, timelines): + super().__init__(id, "US", state, coordinates, last_updated, timelines) + + self.state = state + self.county = county + self.product = NYTProduct() + + def serialize(self, timelines=False): # pylint: disable=arguments-differ,unused-argument + """ + Serializes the location into a dict. + + :returns: The serialized location. + :rtype: dict + """ + serialized = super().serialize(timelines) + + # Update with new fields. + serialized.update( + {"state": self.state, "county": self.county,} + ) + + # Return the serialized location. + return serialized + + # def build_part_a(self): + # #this should actually puts the "part" inside the object, part being id, state, country, etc. + # self.product.parts.append('additional parts') + # return self + +class NYTDirector: + """ + The Director, building a complex representation. + """ + def construct(): + return NYTLocationBuilder()#\ + # # build and add part_a, ie. id; part_b, ie. state; etc. + # .build_part_a() + + +class NYTProduct(): + """ + The Product + """ + def __init__(self): + """ + the NYT product should be empty when the init method is first called + containing a parts attribute + which is of type dictionary + """ + self.parts = {} + pass + + +# # The Client +# #client code that initiates the construction process +# #this should belong where all the other client codes are +# PRODUCT = NYTDirector.construct() \ No newline at end of file