diff --git a/.all-contributorsrc b/.all-contributorsrc
index 0f568cbd..bbf06353 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -153,6 +153,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "ibhuiyan17",
+ "name": "Ibtida Bhuiyan",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/33792969?v=4",
+ "profile": "http://ibtida.me",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/Makefile b/Makefile
index 78f8f6b4..7059a9be 100644
--- a/Makefile
+++ b/Makefile
@@ -21,9 +21,7 @@ lint:
pylint $(APP) || true
fmt:
- isort --apply --atomic
- black . -l 120
+ invoke fmt
check-fmt:
- isort -rc --check
- black . --check --diff
+ invoke check --fmt --sort
diff --git a/Pipfile b/Pipfile
index 6b30ef90..21965c37 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,6 +6,7 @@ verify_ssl = true
[dev-packages]
bandit = "*"
black = "==19.10b0"
+invoke = "*"
isort = "*"
pytest = "*"
pylint = "*"
@@ -27,5 +28,7 @@ python_version = "3.8"
[scripts]
dev = "uvicorn app.main:APP --reload"
start = "uvicorn app.main:APP"
-fmt = "black . -l 120"
-sort = "isort --apply --atomic"
+fmt = "invoke fmt"
+sort = "invoke sort"
+lint = "invoke lint"
+test = "invoke test"
diff --git a/Pipfile.lock b/Pipfile.lock
index c949f6cb..cc93e092 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ecd83aad2c3783fdaa5581f562d022a6b500b3f3b4beb7c3f63d3d5baff85813"
+ "sha256": "9574394caac3b437d4357507a93178b38289861241165e4736369a7b4c2a2cbc"
},
"pipfile-spec": 6,
"requires": {
@@ -342,6 +342,15 @@
],
"version": "==3.1.0"
},
+ "invoke": {
+ "hashes": [
+ "sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132",
+ "sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134",
+ "sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d"
+ ],
+ "index": "pypi",
+ "version": "==1.4.1"
+ },
"isort": {
"hashes": [
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
diff --git a/README.md b/README.md
index 72a30c6a..18ad6645 100644
--- a/README.md
+++ b/README.md
@@ -391,15 +391,22 @@ These are the available API wrappers created by the community. They are not nece
You will need the following things properly installed on your computer.
* [Python 3](https://www.python.org/downloads/) (with pip)
-* [Flask](https://pypi.org/project/Flask/)
* [pipenv](https://pypi.org/project/pipenv/)
## Installation
* `git clone https://github.com/ExpDev07/coronavirus-tracker-api.git`
* `cd coronavirus-tracker-api`
-* `pipenv shell`
-* `pipenv install`
+
+1. Make sure you have [`python3.8` installed and on your `PATH`](https://docs.python-guide.org/starting/installation/).
+2. [Install the `pipenv` dependency manager](https://pipenv.readthedocs.io/en/latest/install/#installing-pipenv)
+ * with [pipx](https://pipxproject.github.io/pipx/) `$ pipx install pipenv`
+ * with [Homebrew/Linuxbrew](https://pipenv.readthedocs.io/en/latest/install/#homebrew-installation-of-pipenv) `$ brew install pipenv`
+ * with [pip/pip3 directly](https://pipenv.readthedocs.io/en/latest/install/#pragmatic-installation-of-pipenv) `$ pip install --user pipenv`
+3. Create virtual environment and install all dependencies `$ pipenv sync --dev`
+4. Activate/enter the virtual environment `$ pipenv shell`
+
+And don't despair if don't get the python setup working on the first try. No one did. Guido got pretty close... once. But that's another story. Good luck.
## Running / Development
@@ -407,12 +414,26 @@ You will need the following things properly installed on your computer.
* Visit your app at [http://localhost:5000](http://localhost:5000).
### Running Tests
+> pytest
+
+```bash
+pipenv run test
+```
-* `make test`
### Linting
+> pylint
-* `make lint`
+```bash
+pipenv run lint
+```
+
+### Formatting
+> black
+
+```bash
+pipenv run fmt
+```
### Building
@@ -446,6 +467,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Turreted đź’» |
+  Ibtida Bhuiyan đź’» |
diff --git a/app/location/__init__.py b/app/location/__init__.py
index cafcf547..4782fddb 100644
--- a/app/location/__init__.py
+++ b/app/location/__init__.py
@@ -1,5 +1,5 @@
from ..coordinates import Coordinates
-from ..utils import countrycodes
+from ..utils import countries
from ..utils.populations import country_population
@@ -31,7 +31,7 @@ def country_code(self):
:returns: The country code.
:rtype: str
"""
- return (countrycodes.country_code(self.country) or countrycodes.default_code).upper()
+ return (countries.country_code(self.country) or countries.default_country_code).upper()
@property
def country_population(self):
diff --git a/app/main.py b/app/main.py
index 44876182..f977622d 100644
--- a/app/main.py
+++ b/app/main.py
@@ -10,7 +10,6 @@
import uvicorn
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
-from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.responses import JSONResponse
from .core import create_app
@@ -18,7 +17,8 @@
from .models.latest import LatestResponse as Latest
from .models.location import LocationResponse as Location
from .models.location import LocationsResponse as Locations
-from .router import router
+from .router.v1 import router as v1router
+from .router.v2 import router as v2router
# ############
# FastAPI App
@@ -83,11 +83,9 @@ async def handle_validation_error(request: Request, exc: pydantic.error_wrappers
# Include routers.
-APP.include_router(router, prefix="/v2", tags=["v2"])
+APP.include_router(v1router, prefix="", tags=["v1"])
+APP.include_router(v2router, prefix="/v2", tags=["v2"])
-# mount the existing Flask app
-# v1 @ /
-APP.mount("/", WSGIMiddleware(create_app()))
# Running of app.
if __name__ == "__main__":
diff --git a/app/router/__init__.py b/app/router/__init__.py
index eefb5f0a..e37fdd9f 100644
--- a/app/router/__init__.py
+++ b/app/router/__init__.py
@@ -1,7 +1,6 @@
from fastapi import APIRouter
-# Create the router.
-router = APIRouter()
+from .v1 import all, confirmed, deaths, recovered
# The routes.
-from . import latest, sources, locations # isort:skip
+from .v2 import latest, sources, locations # isort:skip
diff --git a/app/router/v1/__init__.py b/app/router/v1/__init__.py
new file mode 100644
index 00000000..af9233c5
--- /dev/null
+++ b/app/router/v1/__init__.py
@@ -0,0 +1,3 @@
+from fastapi import APIRouter
+
+router = APIRouter()
diff --git a/app/router/v1/all.py b/app/router/v1/all.py
new file mode 100644
index 00000000..e528ed5a
--- /dev/null
+++ b/app/router/v1/all.py
@@ -0,0 +1,19 @@
+from ...services.location.jhu import get_category
+from . import router
+
+
+@router.get("/all")
+def all():
+ # Get all the categories.
+ confirmed = get_category("confirmed")
+ deaths = get_category("deaths")
+ recovered = get_category("recovered")
+
+ return {
+ # Data.
+ "confirmed": confirmed,
+ "deaths": deaths,
+ "recovered": recovered,
+ # Latest.
+ "latest": {"confirmed": confirmed["latest"], "deaths": deaths["latest"], "recovered": recovered["latest"],},
+ }
diff --git a/app/router/v1/confirmed.py b/app/router/v1/confirmed.py
new file mode 100644
index 00000000..0a8ab1c3
--- /dev/null
+++ b/app/router/v1/confirmed.py
@@ -0,0 +1,9 @@
+from ...services.location.jhu import get_category
+from . import router
+
+
+@router.get("/confirmed")
+def confirmed():
+ confirmed = get_category("confirmed")
+
+ return confirmed
diff --git a/app/router/v1/deaths.py b/app/router/v1/deaths.py
new file mode 100644
index 00000000..b3d90413
--- /dev/null
+++ b/app/router/v1/deaths.py
@@ -0,0 +1,9 @@
+from ...services.location.jhu import get_category
+from . import router
+
+
+@router.get("/deaths")
+def deaths():
+ deaths = get_category("deaths")
+
+ return deaths
diff --git a/app/router/v1/recovered.py b/app/router/v1/recovered.py
new file mode 100644
index 00000000..e9ae8f72
--- /dev/null
+++ b/app/router/v1/recovered.py
@@ -0,0 +1,9 @@
+from ...services.location.jhu import get_category
+from . import router
+
+
+@router.get("/recovered")
+def recovered():
+ recovered = get_category("recovered")
+
+ return recovered
diff --git a/app/router/v2/__init__.py b/app/router/v2/__init__.py
new file mode 100644
index 00000000..af9233c5
--- /dev/null
+++ b/app/router/v2/__init__.py
@@ -0,0 +1,3 @@
+from fastapi import APIRouter
+
+router = APIRouter()
diff --git a/app/router/latest.py b/app/router/v2/latest.py
similarity index 86%
rename from app/router/latest.py
rename to app/router/v2/latest.py
index 81b254cf..8e2e561b 100644
--- a/app/router/latest.py
+++ b/app/router/v2/latest.py
@@ -1,7 +1,7 @@
from fastapi import Request
-from ..enums.sources import Sources
-from ..models.latest import LatestResponse as Latest
+from ...enums.sources import Sources
+from ...models.latest import LatestResponse as Latest
from . import router
diff --git a/app/router/locations.py b/app/router/v2/locations.py
similarity index 92%
rename from app/router/locations.py
rename to app/router/v2/locations.py
index af4b1cfd..2fde5c9e 100644
--- a/app/router/locations.py
+++ b/app/router/v2/locations.py
@@ -1,8 +1,8 @@
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 ...enums.sources import Sources
+from ...models.location import LocationResponse as Location
+from ...models.location import LocationsResponse as Locations
from . import router
diff --git a/app/router/sources.py b/app/router/v2/sources.py
similarity index 85%
rename from app/router/sources.py
rename to app/router/v2/sources.py
index 538921f4..4ade2fef 100644
--- a/app/router/sources.py
+++ b/app/router/v2/sources.py
@@ -1,4 +1,4 @@
-from ..data import data_sources
+from ...data import data_sources
from . import router
diff --git a/app/routes/__init__.py b/app/routes/__init__.py
deleted file mode 100644
index 2a584490..00000000
--- a/app/routes/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
-app.routes
-
-isort:skip_file
-"""
-from flask import Blueprint, redirect, request, abort, current_app as app
-from ..data import data_source
-
-# Follow the import order to avoid circular dependency
-api_v1 = Blueprint("api_v1", __name__, url_prefix="")
-
-# API version 1.
-from .v1 import confirmed, deaths, recovered, all
diff --git a/app/routes/v1/__init__.py b/app/routes/v1/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/app/routes/v1/all.py b/app/routes/v1/all.py
deleted file mode 100644
index 9638c4bd..00000000
--- a/app/routes/v1/all.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from flask import jsonify
-
-from ...routes import api_v1 as api
-from ...services.location.jhu import get_category
-
-
-@api.route("/all")
-def all():
- # Get all the categories.
- confirmed = get_category("confirmed")
- deaths = get_category("deaths")
- recovered = get_category("recovered")
-
- return jsonify(
- {
- # Data.
- "confirmed": confirmed,
- "deaths": deaths,
- "recovered": recovered,
- # Latest.
- "latest": {"confirmed": confirmed["latest"], "deaths": deaths["latest"], "recovered": recovered["latest"],},
- }
- )
diff --git a/app/routes/v1/confirmed.py b/app/routes/v1/confirmed.py
deleted file mode 100644
index 85cfe039..00000000
--- a/app/routes/v1/confirmed.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from flask import jsonify
-
-from ...routes import api_v1 as api
-from ...services.location.jhu import get_category
-
-
-@api.route("/confirmed")
-def confirmed():
- return jsonify(get_category("confirmed"))
diff --git a/app/routes/v1/deaths.py b/app/routes/v1/deaths.py
deleted file mode 100644
index cb65874b..00000000
--- a/app/routes/v1/deaths.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from flask import jsonify
-
-from ...routes import api_v1 as api
-from ...services.location.jhu import get_category
-
-
-@api.route("/deaths")
-def deaths():
- return jsonify(get_category("deaths"))
diff --git a/app/routes/v1/recovered.py b/app/routes/v1/recovered.py
deleted file mode 100644
index be5fe646..00000000
--- a/app/routes/v1/recovered.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from flask import jsonify
-
-from ...routes import api_v1 as api
-from ...services.location.jhu import get_category
-
-
-@api.route("/recovered")
-def recovered():
- return jsonify(get_category("recovered"))
diff --git a/app/services/location/jhu.py b/app/services/location/jhu.py
index 9fde386f..ef99dddc 100644
--- a/app/services/location/jhu.py
+++ b/app/services/location/jhu.py
@@ -7,7 +7,7 @@
from ...coordinates import Coordinates
from ...location import TimelinedLocation
from ...timeline import Timeline
-from ...utils import countrycodes
+from ...utils import countries
from ...utils import date as date_util
from . import LocationService
@@ -80,7 +80,7 @@ def get_category(category):
{
# General info.
"country": country,
- "country_code": countrycodes.country_code(country),
+ "country_code": countries.country_code(country),
"province": item["Province/State"],
# Coordinates.
"coordinates": {"lat": item["Lat"], "long": item["Long"],},
diff --git a/app/utils/countrycodes.py b/app/utils/countries.py
similarity index 73%
rename from app/utils/countrycodes.py
rename to app/utils/countries.py
index f3e90e8f..6647e679 100644
--- a/app/utils/countrycodes.py
+++ b/app/utils/countries.py
@@ -4,13 +4,13 @@
LOGGER = logging.getLogger(__name__)
# Default country code.
-default_code = "XX"
+default_country_code = "XX"
# Mapping of country names to alpha-2 codes according to
# https://en.wikipedia.org/wiki/ISO_3166-1.
# As a reference see also https://github.com/TakahikoKawasaki/nv-i18n (in Java)
# fmt: off
-is_3166_1 = {
+country_name__country_code = {
"Afghanistan" : "AF",
"Ă…land Islands" : "AX",
"Albania" : "AL",
@@ -27,7 +27,10 @@
"Australia" : "AU",
"Austria" : "AT",
"Azerbaijan" : "AZ",
+ " Azerbaijan" : "AZ",
"Bahamas" : "BS",
+ "The Bahamas" : "BS",
+ "Bahamas, The" : "BS",
"Bahrain" : "BH",
"Bangladesh" : "BD",
"Barbados" : "BB",
@@ -38,13 +41,18 @@
"Bermuda" : "BM",
"Bhutan" : "BT",
"Bolivia, Plurinational State of" : "BO",
+ "Bolivia" : "BO",
"Bonaire, Sint Eustatius and Saba" : "BQ",
+ "Caribbean Netherlands" : "BQ",
"Bosnia and Herzegovina" : "BA",
+ # "Bosnia–Herzegovina" : "BA",
+ "Bosnia" : "BA",
"Botswana" : "BW",
"Bouvet Island" : "BV",
"Brazil" : "BR",
"British Indian Ocean Territory" : "IO",
"Brunei Darussalam" : "BN",
+ "Brunei" : "BN",
"Bulgaria" : "BG",
"Burkina Faso" : "BF",
"Burundi" : "BI",
@@ -52,29 +60,40 @@
"Cameroon" : "CM",
"Canada" : "CA",
"Cape Verde" : "CV",
+ "Cabo Verde" : "CV",
"Cayman Islands" : "KY",
"Central African Republic" : "CF",
"Chad" : "TD",
"Chile" : "CL",
"China" : "CN",
+ "Mainland China" : "CN",
"Christmas Island" : "CX",
"Cocos (Keeling) Islands" : "CC",
"Colombia" : "CO",
"Comoros" : "KM",
"Congo" : "CG",
+ "Congo (Brazzaville)" : "CG",
+ "Republic of the Congo" : "CG",
"Congo, the Democratic Republic of the" : "CD",
+ "Congo (Kinshasa)" : "CD",
+ "DR Congo" : "CD",
"Cook Islands" : "CK",
"Costa Rica" : "CR",
"CĂ´te d'Ivoire" : "CI",
+ "Cote d'Ivoire" : "CI",
+ "Ivory Coast" : "CI",
"Croatia" : "HR",
"Cuba" : "CU",
"Curaçao" : "CW",
+ "Curacao" : "CW",
"Cyprus" : "CY",
"Czech Republic" : "CZ",
+ "Czechia" : "CZ",
"Denmark" : "DK",
"Djibouti" : "DJ",
"Dominica" : "DM",
"Dominican Republic" : "DO",
+ "Dominican Rep" : "DO",
"Ecuador" : "EC",
"Egypt" : "EG",
"El Salvador" : "SV",
@@ -83,7 +102,9 @@
"Estonia" : "EE",
"Ethiopia" : "ET",
"Falkland Islands (Malvinas)" : "FK",
+ "Falkland Islands" : "FK",
"Faroe Islands" : "FO",
+ "Faeroe Islands" : "FO",
"Fiji" : "FJ",
"Finland" : "FI",
"France" : "FR",
@@ -92,8 +113,11 @@
"French Southern Territories" : "TF",
"Gabon" : "GA",
"Gambia" : "GM",
+ "The Gambia" : "GM",
+ "Gambia, The" : "GM",
"Georgia" : "GE",
"Germany" : "DE",
+ "Deutschland" : "DE",
"Ghana" : "GH",
"Gibraltar" : "GI",
"Greece" : "GR",
@@ -109,31 +133,49 @@
"Haiti" : "HT",
"Heard Island and McDonald Islands" : "HM",
"Holy See (Vatican City State)" : "VA",
+ "Holy See" : "VA",
+ "Vatican City" : "VA",
"Honduras" : "HN",
"Hong Kong" : "HK",
+ "Hong Kong SAR" : "HK",
"Hungary" : "HU",
"Iceland" : "IS",
"India" : "IN",
"Indonesia" : "ID",
"Iran, Islamic Republic of" : "IR",
+ "Iran" : "IR",
+ "Iran (Islamic Republic of)" : "IR",
"Iraq" : "IQ",
"Ireland" : "IE",
+ "Republic of Ireland" : "IE",
"Isle of Man" : "IM",
"Israel" : "IL",
"Italy" : "IT",
"Jamaica" : "JM",
"Japan" : "JP",
"Jersey" : "JE",
+ # Guernsey and Jersey form Channel Islands. Conjoin Guernsey on Jersey.
+ # Jersey has higher population.
+ # https://en.wikipedia.org/wiki/Channel_Islands
+ "Guernsey and Jersey" : "JE",
+ "Channel Islands" : "JE",
+ # "Channel Islands" : "GB",
"Jordan" : "JO",
"Kazakhstan" : "KZ",
"Kenya" : "KE",
"Kiribati" : "KI",
"Korea, Democratic People's Republic of" : "KP",
+ "North Korea" : "KP",
"Korea, Republic of" : "KR",
+ "Korea, South" : "KR",
+ "South Korea" : "KR",
+ "Republic of Korea" : "KR",
"Kosovo, Republic of" : "XK",
+ "Kosovo" : "XK",
"Kuwait" : "KW",
"Kyrgyzstan" : "KG",
"Lao People's Democratic Republic" : "LA",
+ "Laos" : "LA",
"Latvia" : "LV",
"Lebanon" : "LB",
"Lesotho" : "LS",
@@ -143,7 +185,11 @@
"Lithuania" : "LT",
"Luxembourg" : "LU",
"Macao" : "MO",
+ # TODO Macau is probably a typo. Report it to CSSEGISandData/COVID-19
+ "Macau" : "MO",
+ "Macao SAR" : "MO",
"North Macedonia" : "MK",
+ "Macedonia" : "MK",
"Madagascar" : "MG",
"Malawi" : "MW",
"Malaysia" : "MY",
@@ -157,7 +203,11 @@
"Mayotte" : "YT",
"Mexico" : "MX",
"Micronesia, Federated States of" : "FM",
+ "F.S. Micronesia" : "FM",
+ "Micronesia" : "FM",
"Moldova, Republic of" : "MD",
+ "Republic of Moldova" : "MD",
+ "Moldova" : "MD",
"Monaco" : "MC",
"Mongolia" : "MN",
"Montenegro" : "ME",
@@ -182,6 +232,11 @@
"Pakistan" : "PK",
"Palau" : "PW",
"Palestine, State of" : "PS",
+ "Palestine" : "PS",
+ "occupied Palestinian territory" : "PS",
+ "State of Palestine" : "PS",
+ "The West Bank and Gaza" : "PS",
+ "West Bank and Gaza" : "PS",
"Panama" : "PA",
"Papua New Guinea" : "PG",
"Paraguay" : "PY",
@@ -193,19 +248,30 @@
"Puerto Rico" : "PR",
"Qatar" : "QA",
"Réunion" : "RE",
+ "Reunion" : "RE",
"Romania" : "RO",
"Russian Federation" : "RU",
+ "Russia" : "RU",
"Rwanda" : "RW",
"Saint Barthélemy" : "BL",
+ "Saint Barthelemy" : "BL",
"Saint Helena, Ascension and Tristan da Cunha" : "SH",
+ "Saint Helena" : "SH",
"Saint Kitts and Nevis" : "KN",
+ "Saint Kitts & Nevis" : "KN",
"Saint Lucia" : "LC",
"Saint Martin (French part)" : "MF",
+ "Saint Martin" : "MF",
+ "St. Martin" : "MF",
"Saint Pierre and Miquelon" : "PM",
+ "Saint Pierre & Miquelon" : "PM",
"Saint Vincent and the Grenadines" : "VC",
+ "St. Vincent & Grenadines" : "VC",
"Samoa" : "WS",
"San Marino" : "SM",
"Sao Tome and Principe" : "ST",
+ "SĂŁo TomĂ© and PrĂncipe" : "ST",
+ "Sao Tome & Principe" : "ST",
"Saudi Arabia" : "SA",
"Senegal" : "SN",
"Serbia" : "RS",
@@ -213,6 +279,7 @@
"Sierra Leone" : "SL",
"Singapore" : "SG",
"Sint Maarten (Dutch part)" : "SX",
+ "Sint Maarten" : "SX",
"Slovakia" : "SK",
"Slovenia" : "SI",
"Solomon Islands" : "SB",
@@ -226,14 +293,21 @@
"Suriname" : "SR",
"Svalbard and Jan Mayen" : "SJ",
"Eswatini" : "SZ", # previous name "Swaziland"
+ "Swaziland" : "SZ",
"Sweden" : "SE",
"Switzerland" : "CH",
"Syrian Arab Republic" : "SY",
+ "Syria" : "SY",
"Taiwan, Province of China" : "TW",
+ "Taiwan*" : "TW",
+ "Taipei and environs" : "TW",
+ "Taiwan" : "TW",
"Tajikistan" : "TJ",
"Tanzania, United Republic of" : "TZ",
+ "Tanzania" : "TZ",
"Thailand" : "TH",
"Timor-Leste" : "TL",
+ "East Timor" : "TL",
"Togo" : "TG",
"Tokelau" : "TK",
"Tonga" : "TO",
@@ -242,21 +316,32 @@
"Turkey" : "TR",
"Turkmenistan" : "TM",
"Turks and Caicos Islands" : "TC",
+ "Turks and Caicos" : "TC",
"Tuvalu" : "TV",
"Uganda" : "UG",
"Ukraine" : "UA",
"United Arab Emirates" : "AE",
+ "Emirates" : "AE",
"United Kingdom" : "GB",
+ "UK" : "GB",
+ # Conjoin North Ireland on United Kingdom
+ "North Ireland" : "GB",
"United States" : "US",
+ "US" : "US",
"United States Minor Outlying Islands" : "UM",
"Uruguay" : "UY",
"Uzbekistan" : "UZ",
"Vanuatu" : "VU",
"Venezuela, Bolivarian Republic of" : "VE",
+ "Venezuela" : "VE",
"Viet Nam" : "VN",
+ "Vietnam" : "VN",
"Virgin Islands, British" : "VG",
+ "British Virgin Islands" : "VG",
"Virgin Islands, U.S." : "VI",
+ "U.S. Virgin Islands" : "VI",
"Wallis and Futuna" : "WF",
+ "Wallis & Futuna" : "WF",
"Western Sahara" : "EH",
"Yemen" : "YE",
"Zambia" : "ZM",
@@ -265,123 +350,26 @@
# see also
# https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent_(data_file)#Data_file
# https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent
- "United Nations Neutral Zone" : "XD",
- "Iraq-Saudi Arabia Neutral Zone" : "XE",
- "Spratly Islands" : "XS",
+ "United Nations Neutral Zone" : "XD",
+ "Iraq-Saudi Arabia Neutral Zone" : "XE",
+ "Spratly Islands" : "XS",
- # TODO "Disputed Territory" conflicts with `default_code`
- # "Disputed Territory" : "XX",
-}
+ # "Diamond Princess" : default_country_code,
+ # TODO "Disputed Territory" conflicts with `default_country_code`
+ # "Disputed Territory" : "XX",
-# Mapping of alternative names, spelling, typos to the names of countries used
-# by the ISO 3166-1 norm
-synonyms = {
- "Mainland China" : "China",
- "Czechia" : "Czech Republic",
- "Channel Islands" : "United Kingdom",
- "Republic of Korea" : "Korea, Republic of",
- "Republic of Moldova" : "Moldova, Republic of",
- "Taiwan" : "Taiwan, Province of China",
- "US" : "United States",
- # TODO Macau is probably a typo. Report it to CSSEGISandData/COVID-19
- "Macau" : "Macao",
- "Macao SAR" : "Macao",
- "Vietnam" : "Viet Nam",
- "UK" : "United Kingdom",
- "Russia" : "Russian Federation",
- "Iran (Islamic Republic of)" : "Iran, Islamic Republic of",
- "Saint Barthelemy" : "Saint Barthélemy",
- "Saint Martin" : "Saint Martin (French part)",
- "Palestine" : "Palestine, State of",
- "occupied Palestinian territory" : "Palestine, State of",
- "State of Palestine" : "Palestine, State of",
- "The West Bank and Gaza" : "Palestine, State of",
- "Holy See" : "Holy See (Vatican City State)",
- "Brunei" : "Brunei Darussalam",
- "Hong Kong SAR" : "Hong Kong",
- "Taipei and environs" : "Taiwan, Province of China",
- "South Korea" : "Korea, Republic of",
- "Iran" : "Iran, Islamic Republic of",
- "Vatican City" : "Holy See (Vatican City State)",
- "DR Congo" : "Congo, the Democratic Republic of the",
- "Republic of the Congo" : "Congo",
- "Tanzania" : "Tanzania, United Republic of",
- "Venezuela" : "Venezuela, Bolivarian Republic of",
- "North Korea" : "Korea, Democratic People's Republic of",
- "Syria" : "Syrian Arab Republic",
- "Bolivia" : "Bolivia, Plurinational State of",
- "Laos" : "Lao People's Democratic Republic",
- "Moldova" : "Moldova, Republic of",
- "Eswatini" : "Swaziland",
- "Cabo Verde" : "Cape Verde",
- "Sao Tome & Principe" : "Sao Tome and Principe",
- "Micronesia" : "Micronesia, Federated States of",
- "St. Vincent & Grenadines" : "Saint Vincent and the Grenadines",
- "U.S. Virgin Islands" : "Virgin Islands, U.S.",
- "Saint Kitts & Nevis" : "Saint Kitts and Nevis",
- "Faeroe Islands" : "Faroe Islands",
- "Sint Maarten" : "Sint Maarten (Dutch part)",
- "Turks and Caicos" : "Turks and Caicos Islands",
- "Saint Martin" : "Saint Martin (French part)",
- "British Virgin Islands" : "Virgin Islands, British",
- "Wallis & Futuna" : "Wallis and Futuna",
- "Saint Helena" : "Saint Helena, Ascension and Tristan da Cunha",
- "Saint Pierre & Miquelon" : "Saint Pierre and Miquelon",
- "Falkland Islands" : "Falkland Islands (Malvinas)",
- "Republic of Ireland" : "Ireland",
- "Ivory Coast" : "CĂ´te d'Ivoire",
- " Azerbaijan" : "Azerbaijan",
- # Conjoin North Ireland on United Kingdom
- "North Ireland" : "United Kingdom",
- "East Timor" : "Timor-Leste",
- "SĂŁo TomĂ© and PrĂncipe" : "Sao Tome and Principe",
- # Guernsey and Jersey form Channel Islands. Conjoin Guernsey on Jersey.
- # Jersey has higher population.
- # https://en.wikipedia.org/wiki/Channel_Islands
- "Guernsey and Jersey" : "Jersey",
- "Channel Islands" : "Jersey",
- "Caribbean Netherlands" : "Bonaire, Sint Eustatius and Saba",
- "F.S. Micronesia" : "Micronesia, Federated States of",
- "Emirates" : "United Arab Emirates",
- # "Bosnia–Herzegovina" : "Bosnia and Herzegovina",
- "Bosnia" : "Bosnia and Herzegovina",
- "Dominican Rep" : "Dominican Republic",
- "Macedonia" : "North Macedonia",
- "Korea, South" : "Korea, Republic of",
- "Cote d'Ivoire" : "CĂ´te d'Ivoire",
- "St. Martin" : "Saint Martin (French part)",
- "Congo (Kinshasa)" : "Congo, the Democratic Republic of the",
- "Taiwan*" : "Taiwan, Province of China",
- "Reunion" : "Réunion",
- "Curacao" : "Curaçao",
- "Congo (Brazzaville)" : "Congo",
- "Deutschland" : "Germany",
- "The Bahamas" : "Bahamas",
- "The Gambia" : "Gambia",
- "Kosovo" : "Kosovo, Republic of",
- "Swaziland" : "Eswatini",
- "Gambia, The" : "Gambia",
- "Bahamas, The" : "Bahamas",
# "Others" has no mapping, i.e. the default val is used
# "Cruise Ship" has no mapping, i.e. the default val is used
}
# fmt: on
-def country_code(country):
+def country_code(s):
"""
Return two letter country code (Alpha-2) according to https://en.wikipedia.org/wiki/ISO_3166-1
Defaults to "XX".
"""
- # Look in synonyms if not found.
- if not country in is_3166_1 and country in synonyms:
- country = synonyms[country]
-
- # Get country or fallback to default_code.
- country_code = is_3166_1.get(country, default_code)
-
- # Default picked?
- if country_code == default_code:
- LOGGER.warning(f"No country_code found for '{country}'. Using '{country_code}'!")
+ country_code = country_name__country_code.get(s, default_country_code)
+ if country_code == default_country_code:
+ LOGGER.warning(f"No country code found for '{s}'. Using '{country_code}'!")
- # Return.
return country_code
diff --git a/app/utils/populations.py b/app/utils/populations.py
index 8a78ec50..ea72c334 100644
--- a/app/utils/populations.py
+++ b/app/utils/populations.py
@@ -5,7 +5,7 @@
import requests
from cachetools import TTLCache, cached
-from .countrycodes import country_code
+from .countries import country_code
LOGGER = logging.getLogger(__name__)
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 00000000..3ff5f24c
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,66 @@
+"""
+tasks.py
+--------
+Project invoke tasks
+
+Available commands
+ invoke --list
+ invoke fmt
+ invoke sort
+ invoke check
+"""
+import invoke
+
+TARGETS_DESCRIPTION = "Paths/directories to format. [default: . ]"
+
+
+@invoke.task(help={"targets": TARGETS_DESCRIPTION})
+def sort(ctx, targets="."):
+ """Sort module imports."""
+ print("sorting imports ...")
+ args = ["isort", "-rc", "--atomic", targets]
+ ctx.run(" ".join(args))
+
+
+@invoke.task(pre=[sort], help={"targets": TARGETS_DESCRIPTION})
+def fmt(ctx, targets="."):
+ """Format python source code & sort imports."""
+ print("formatting ...")
+ args = ["black", targets]
+ ctx.run(" ".join(args))
+
+
+@invoke.task
+def check(ctx, fmt=False, sort=False, diff=False): # pylint: disable=redefined-outer-name
+ """Check code format and import order."""
+ if not any([fmt, sort]):
+ fmt = True
+ sort = True
+
+ fmt_args = ["black", "--check", "."]
+ sort_args = ["isort", "-rc", "--check", "."]
+
+ if diff:
+ fmt_args.append("--diff")
+ sort_args.append("--diff")
+
+ cmd_args = []
+ if fmt:
+ cmd_args.extend(fmt_args)
+ if sort:
+ if cmd_args:
+ cmd_args.append("&")
+ cmd_args.extend(sort_args)
+ ctx.run(" ".join(cmd_args))
+
+
+@invoke.task
+def lint(ctx):
+ """Run linter."""
+ ctx.run(" ".join(["pylint", "app"]))
+
+
+@invoke.task
+def test(ctx):
+ """Run pytest tests."""
+ ctx.run(" ".join(["pytest", "-v"]))
diff --git a/tests/test_countries.py b/tests/test_countries.py
new file mode 100644
index 00000000..2c9ba65e
--- /dev/null
+++ b/tests/test_countries.py
@@ -0,0 +1,24 @@
+import pytest
+
+from app.utils import countries
+
+
+"""
+Todo:
+ * Test cases for capturing of stdout/stderr
+"""
+
+
+@pytest.mark.parametrize(
+ "country_name,expected_country_code",
+ [
+ ("Germany", "DE"),
+ ("Bolivia, Plurinational State of", "BO"),
+ ("Korea, Democratic People's Republic of", "KP"),
+ ("US", "US"),
+ ("BlaBla", countries.default_country_code),
+ ("Others", countries.default_country_code),
+ ],
+)
+def test_countries_country_name__country_code(country_name, expected_country_code):
+ assert countries.country_code(country_name) == expected_country_code
diff --git a/tests/test_countrycodes.py b/tests/test_countrycodes.py
deleted file mode 100644
index 1b132266..00000000
--- a/tests/test_countrycodes.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import pytest
-
-from app.utils import countrycodes
-
-
-"""
-Todo:
- * Test cases for capturing of stdout/stderr
-"""
-
-
-@pytest.mark.parametrize(
- "country_name,expected_country_code",
- [
- ("Germany", "DE"),
- ("Bolivia, Plurinational State of", "BO"),
- ("Korea, Democratic People's Republic of", "KP"),
- ("BlaBla", "XX"),
- ],
-)
-def test_countrycodes_is_3166_1(country_name, expected_country_code):
- assert countrycodes.country_code(country_name) == expected_country_code
-
-
-@pytest.mark.parametrize(
- "country_name_synonym, expected_country_code",
- [("Deutschland", "DE"), ("Iran (Islamic Republic of)", "IR"), ("British Virgin Islands", "VG")],
-)
-def test_countrycodes_synonym(country_name_synonym, expected_country_code):
- assert countrycodes.country_code(country_name_synonym) == expected_country_code
diff --git a/tests/test_routes.py b/tests/test_routes.py
index 9e1c03ef..48d804e5 100644
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -6,8 +6,8 @@
import pytest
from fastapi.testclient import TestClient
-import app
-from app import services
+# import app
+# from app import services
from app.main import APP
from .test_jhu import DATETIME_STRING, mocked_requests_get, mocked_strptime_isoformat
@@ -22,11 +22,7 @@ class FlaskRoutesTest(unittest.TestCase):
Store all integration testcases in one class to ensure app context
"""
- # load app context only once.
- app = app.create_app()
-
def setUp(self):
- self.client = FlaskRoutesTest.app.test_client()
self.asgi_client = TestClient(APP)
self.date = DATETIME_STRING
@@ -48,36 +44,36 @@ def test_v1_confirmed(self, mock_request_get, mock_datetime):
mock_datetime.strptime.side_effect = mocked_strptime_isoformat
state = "confirmed"
expected_json_output = self.read_file_v1(state=state)
- return_data = self.client.get("/{}".format(state)).data.decode()
+ return_data = self.asgi_client.get("/{}".format(state)).json()
- assert return_data == expected_json_output
+ assert return_data == json.loads(expected_json_output)
def test_v1_deaths(self, mock_request_get, mock_datetime):
mock_datetime.utcnow.return_value.isoformat.return_value = self.date
mock_datetime.strptime.side_effect = mocked_strptime_isoformat
state = "deaths"
expected_json_output = self.read_file_v1(state=state)
- return_data = self.client.get("/{}".format(state)).data.decode()
+ return_data = self.asgi_client.get("/{}".format(state)).json()
- assert return_data == expected_json_output
+ assert return_data == json.loads(expected_json_output)
def test_v1_recovered(self, mock_request_get, mock_datetime):
mock_datetime.utcnow.return_value.isoformat.return_value = self.date
mock_datetime.strptime.side_effect = mocked_strptime_isoformat
state = "recovered"
expected_json_output = self.read_file_v1(state=state)
- return_data = self.client.get("/{}".format(state)).data.decode()
+ return_data = self.asgi_client.get("/{}".format(state)).json()
- assert return_data == expected_json_output
+ assert return_data == json.loads(expected_json_output)
def test_v1_all(self, mock_request_get, mock_datetime):
mock_datetime.utcnow.return_value.isoformat.return_value = self.date
mock_datetime.strptime.side_effect = mocked_strptime_isoformat
state = "all"
expected_json_output = self.read_file_v1(state=state)
- return_data = self.client.get("/{}".format(state)).data.decode()
- # print(return_data)
- assert return_data == expected_json_output
+ return_data = self.asgi_client.get("/{}".format(state)).json()
+
+ assert return_data == json.loads(expected_json_output)
def test_v2_latest(self, mock_request_get, mock_datetime):
mock_datetime.utcnow.return_value.isoformat.return_value = DATETIME_STRING