Adds National Risk Index data to ETL pipeline (#549)

* Adds dev dependencies to requirements.txt and re-runs black on codebase

* Adds test and code for national risk index etl, still in progress

* Removes test_data from .gitignore

* Adds test data to nation_risk_index tests

* Creates tests and ETL class for NRI data

* Adds tests for load() and transform() methods of NationalRiskIndexETL

* Updates README.md with info about the NRI dataset

* Adds to dos

* Moves tests and test data into a tests/ dir in national_risk_index

* Moves tmp_dir for tests into data/tmp/tests/

* Promotes fixtures to conftest and relocates national_risk_index tests:
The relocation of national_risk_index tests is necessary because tests 
can only use fixtures specified in conftests within the same package

* Fixes issue with df.equals() in test_transform()

* Files reformatted by black

* Commit changes to other files after re-running black

* Fixes unused import that caused lint checks to fail

* Moves tests/ directory to app root for data_pipeline
This commit is contained in:
Billy Daly 2021-09-07 20:51:34 -04:00 committed by GitHub
parent 94298635c2
commit f0900f7b69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 307 additions and 7 deletions

View file

@ -39,6 +39,11 @@ DATASET_LIST = [
"module_dir": "cdc_places",
"class_name": "CDCPlacesETL",
},
{
"name": "national_risk_index",
"module_dir": "national_risk_index",
"class_name": "NationalRiskIndexETL",
},
]
CENSUS_INFO = {
"name": "census",

View file

@ -0,0 +1,14 @@
# FEMA National Risk Index
## Description
The [National Risk Index](https://www.fema.gov/flood-maps/products-tools/national-risk-index) is a new, online mapping application from FEMA that identifies communities most at risk to 18 natural hazards. This application visualizes natural hazard risk metrics and includes data about expected annual losses from natural hazards, social vulnerability and community resilience.
The National Risk Index's interactive web maps are at the county and Census tract level and made available via geographic information system (GIS) services for custom analyses. For this project, we've utilized the NRI data collected at the Census tract level
## Data Transformation Summary
The following transformations were applied to the NRI data during the ETL process:
- The `TRACTFIPS` column was renamed to `GEOID10_TRACT` to match the name of columns that hold the Census Tract FIPS code in other data sets
- The NRI score values for each Census tract were applied to each of the Census block groups inside of that Census tract so that the unit of analysis would match that of other datasets like the American Communities Survey

View file

@ -0,0 +1,72 @@
import pandas as pd
from data_pipeline.etl.base import ExtractTransformLoad
from data_pipeline.utils import get_module_logger
logger = get_module_logger(__name__)
class NationalRiskIndexETL(ExtractTransformLoad):
"""ETL class for the FEMA National Risk Index dataset"""
def __init__(self):
self.NRI_FTP_URL = "https://nri-data-downloads.s3.amazonaws.com/NRI_Table_CensusTracts.zip"
self.INPUT_CSV = self.TMP_PATH / "NRI_Table_CensusTracts.csv"
self.OUTPUT_DIR = (
self.DATA_PATH / "dataset" / "national_risk_index_2020"
)
self.BLOCK_GROUP_CSV = (
self.DATA_PATH / "dataset" / "census_acs_2019" / "usa.csv"
)
self.df: pd.DataFrame
def extract(self) -> None:
"""Unzips NRI dataset from the FEMA data source and writes the files
to the temporary data folder for use in the transform() method
"""
logger.info("Downloading National Risk Index Data")
super().extract(
self.NRI_FTP_URL,
self.TMP_PATH,
)
def transform(self) -> None:
"""Reads the unzipped data file into memory and applies the following
transformations to prepare it for the load() method:
- Renames the Census Tract column to match the other datasets
- Applies the NRI score for each Census Tract to the Census Block
Groups inside of that Tract
"""
logger.info("Transforming National Risk Index Data")
NRI_TRACT_COL = "TRACTFIPS" # Census Tract Column in NRI data
TRACT_COL = self.GEOID_TRACT_FIELD_NAME # Census Tract column name
BLOCK_COL = self.GEOID_FIELD_NAME # Census Block Group column name
# read in the unzipped csv from NRI data source then rename the
# Census Tract column for merging
df_nri = pd.read_csv(
self.INPUT_CSV,
dtype={NRI_TRACT_COL: "string"},
na_values=["None"],
low_memory=False,
)
df_nri.rename(columns={NRI_TRACT_COL: TRACT_COL}, inplace=True)
# get the full list of Census Block Groups from the ACS data
# and extract the Census Tract ID from each Block Group ID
df_acs = pd.read_csv(self.BLOCK_GROUP_CSV, dtype={BLOCK_COL: "string"})
df_acs[TRACT_COL] = df_acs[BLOCK_COL].str[0:11]
df_block_group = df_acs[[BLOCK_COL, TRACT_COL]]
# merge NRI data on the Census Tract ID so that each
# Block Group inherits the NRI score of its Census Tract
self.df = df_block_group.merge(df_nri, how="left", on=TRACT_COL)
def load(self) -> None:
"""Writes the NRI data as a csv to the directory at self.OUTPUT_DIR"""
logger.info("Saving National Risk Index CSV")
# write nationwide csv
self.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
self.df.to_csv(self.OUTPUT_DIR / "usa.csv", index=False)

View file

@ -0,0 +1,33 @@
import os
import pytest
from data_pipeline.config import settings
from data_pipeline.etl.base import ExtractTransformLoad
TMP_DIR = settings.APP_ROOT / "data" / "tmp" / "tests"
@pytest.fixture(scope="session")
def mock_paths(tmp_path_factory) -> tuple:
"""Creates new DATA_PATH and TMP_PATH that point to a temporary local
file structure that can be used to mock data folder during testing
"""
# sets location of the temp directory inside the national_risk_index folder
os.environ["PYTEST_DEBUG_TEMPROOT"] = str(TMP_DIR)
TMP_DIR.mkdir(parents=True, exist_ok=True)
# creates DATA_PATH and TMP_PATH directories in temp directory
data_path = tmp_path_factory.mktemp("data", numbered=False)
tmp_path = data_path / "tmp"
tmp_path.mkdir()
return data_path, tmp_path
@pytest.fixture
def mock_etl(monkeypatch, mock_paths) -> None:
"""Creates a mock version of the base ExtractTransformLoad class and resets
global the variables for DATA_PATH and TMP_PATH to the local mock_paths
"""
data_path, tmp_path = mock_paths
monkeypatch.setattr(ExtractTransformLoad, "DATA_PATH", data_path)
monkeypatch.setattr(ExtractTransformLoad, "TMP_PATH", tmp_path)

View file

@ -0,0 +1,11 @@
GEOID10,POPULATION
050070403001,1000
050070403002,1500
050010201001,1000
050010201002,1500
150070405001,2000
150070405002,2250
150010210101,2000
150010210102,1500
150010211011,1750
150010211012,1500
1 GEOID10 POPULATION
2 050070403001 1000
3 050070403002 1500
4 050010201001 1000
5 050010201002 1500
6 150070405001 2000
7 150070405002 2250
8 150010210101 2000
9 150010210102 1500
10 150010211011 1750
11 150010211012 1500

View file

@ -0,0 +1,6 @@
TRACT,TRACTFIPS,RISK_SCORE,RISK_RATNG,RISK_NPCTL
40300,05007040300,10.492015,Very Low,15.3494
20100,05001020100,14.705854,Relatively Low,36.725828
40500,15007040500,10.234981,Very Low,13.997993
21010,15001021010,21.537231,Relatively Moderate,59.488033
21101,15001021101,19.434585,Relatively Low,53.392265
1 TRACT TRACTFIPS RISK_SCORE RISK_RATNG RISK_NPCTL
2 40300 05007040300 10.492015 Very Low 15.3494
3 20100 05001020100 14.705854 Relatively Low 36.725828
4 40500 15007040500 10.234981 Very Low 13.997993
5 21010 15001021010 21.537231 Relatively Moderate 59.488033
6 21101 15001021101 19.434585 Relatively Low 53.392265

View file

@ -0,0 +1,11 @@
GEOID10,GEOID10_TRACT,TRACT,RISK_SCORE,RISK_RATNG,RISK_NPCTL
050070403001,05007040300,40300,10.492015,Very Low,15.3494
050070403002,05007040300,40300,10.492015,Very Low,15.3494
050010201001,05001020100,20100,14.705854,Relatively Low,36.725828
050010201002,05001020100,20100,14.705854,Relatively Low,36.725828
150070405001,15007040500,40500,10.234981,Very Low,13.997993
150070405002,15007040500,40500,10.234981,Very Low,13.997993
150010210101,15001021010,21010,21.537231,Relatively Moderate,59.488033
150010210102,15001021010,21010,21.537231,Relatively Moderate,59.488033
150010211011,15001021101,21101,19.434585,Relatively Low,53.392265
150010211012,15001021101,21101,19.434585,Relatively Low,53.392265
1 GEOID10 GEOID10_TRACT TRACT RISK_SCORE RISK_RATNG RISK_NPCTL
2 050070403001 05007040300 40300 10.492015 Very Low 15.3494
3 050070403002 05007040300 40300 10.492015 Very Low 15.3494
4 050010201001 05001020100 20100 14.705854 Relatively Low 36.725828
5 050010201002 05001020100 20100 14.705854 Relatively Low 36.725828
6 150070405001 15007040500 40500 10.234981 Very Low 13.997993
7 150070405002 15007040500 40500 10.234981 Very Low 13.997993
8 150010210101 15001021010 21010 21.537231 Relatively Moderate 59.488033
9 150010210102 15001021010 21010 21.537231 Relatively Moderate 59.488033
10 150010211011 15001021101 21101 19.434585 Relatively Low 53.392265
11 150010211012 15001021101 21101 19.434585 Relatively Low 53.392265

View file

@ -0,0 +1,110 @@
from pathlib import Path
from shutil import copyfile
import pandas as pd
from data_pipeline.config import settings
from data_pipeline.etl.sources.national_risk_index.etl import (
NationalRiskIndexETL,
)
DATA_DIR = (
settings.APP_ROOT / "tests" / "sources" / "national_risk_index" / "data"
)
def copy_data_files(src: Path, dst: Path) -> None:
"""Copies test data from src Path to dst Path for use in testing
Args
src: pathlib.Path instance. The location of the source data file.
dst: pathlib.Path instance. Where to copy the source data file to.
Returns
None. This is a void function
"""
if not dst.exists():
dst.parent.mkdir(parents=True, exist_ok=True)
copyfile(src, dst)
assert dst.exists()
class TestNationalRiskIndexETL:
def test_init(self, mock_etl, mock_paths):
"""Tests that the mock NationalRiskIndexETL class instance was
initiliazed correctly.
Validates the following conditions:
- self.DATA_PATH points to the "data" folder in the temp directory
- self.TMP_PATH points to the "data/tmp" folder in the temp directory
- self.INPUT_PATH points to the correct path in the temp directory
- self.OUTPUT_PATH points to the correct path in the temp directory
"""
# setup
etl = NationalRiskIndexETL()
data_path, tmp_path = mock_paths
input_csv = tmp_path / "NRI_Table_CensusTracts.csv"
output_dir = data_path / "dataset" / "national_risk_index_2020"
# validation
assert etl.DATA_PATH == data_path
assert etl.TMP_PATH == tmp_path
assert etl.INPUT_CSV == input_csv
assert etl.OUTPUT_DIR == output_dir
assert etl.GEOID_FIELD_NAME == "GEOID10"
assert etl.GEOID_TRACT_FIELD_NAME == "GEOID10_TRACT"
def test_transform(self, mock_etl):
"""Tests the transform() method for NationalRiskIndexETL
Validates the following conditions:
- The columns have been renamed correctly
- The values for each tract has been applied to each of the block
groups in that tract
"""
# setup - copy sample data into tmp_dir
etl = NationalRiskIndexETL()
input_src = DATA_DIR / "input.csv"
input_dst = etl.INPUT_CSV
acs_src = DATA_DIR / "acs.csv"
acs_dst = DATA_DIR / etl.BLOCK_GROUP_CSV
for src, dst in [(input_src, input_dst), (acs_src, acs_dst)]:
copy_data_files(src, dst)
# setup - read in sample output as dataframe
TRACT_COL = etl.GEOID_TRACT_FIELD_NAME
BLOCK_COL = etl.GEOID_FIELD_NAME
expected = pd.read_csv(
DATA_DIR / "output.csv",
dtype={BLOCK_COL: "string", TRACT_COL: "string"},
)
# execution
etl.transform()
# validation
assert etl.df.shape == (10, 6)
assert etl.df.equals(expected)
def test_load(self, mock_etl):
"""Tests the load() method for NationalRiskIndexETL
Validates the following conditions:
- The transformed dataframe is written to the directory specified by
self.OUTPUT_DIR
- The content of the file that's written matches the data in self.df
"""
# setup
etl = NationalRiskIndexETL()
output_path = etl.OUTPUT_DIR / "usa.csv"
TRACT_COL = etl.GEOID_TRACT_FIELD_NAME
BLOCK_COL = etl.GEOID_FIELD_NAME
expected = pd.read_csv(
DATA_DIR / "output.csv",
dtype={BLOCK_COL: str, TRACT_COL: str},
)
etl.df = expected
# execution
etl.load()
output = pd.read_csv(
output_path, dtype={BLOCK_COL: str, TRACT_COL: str}
)
# validation
assert output_path.exists()
assert output.equals(expected)

View file

@ -68,6 +68,9 @@ disable = [
[tool.pylint.FORMAT]
max-line-length = 150
[tool.pylint.typecheck]
generated-members = "pandas.*" # fixes E1101 for ETL.df
[tool.pylint.SIMILARITIES]
# Configures how pylint detects repetitive code
ignore-comments = "yes"

View file

@ -1,9 +1,14 @@
appdirs==1.4.4; python_full_version >= "3.6.2"
appnope==0.1.2; sys_platform == "darwin" and python_version >= "3.7" and platform_system == "Darwin"
argcomplete==1.12.3; python_version < "3.8.0" and python_version >= "3.7"
argon2-cffi==20.1.0; python_version >= "3.6"
astroid==2.6.6; python_version >= "3.6" and python_version < "4.0"
async-generator==1.10; python_full_version >= "3.6.1" and python_version >= "3.7"
atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0"
attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
backcall==0.2.0; python_version >= "3.7"
backports.entry-points-selectable==1.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "2.7"
black==21.7b0; python_full_version >= "3.6.2"
bleach==4.0.0; python_version >= "3.7"
censusdata==1.15; python_version >= "2.7"
certifi==2021.5.30; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7"
@ -12,21 +17,29 @@ charset-normalizer==2.0.4; python_full_version >= "3.6.0" and python_version >=
click-plugins==1.1.1; python_version >= "3.6"
click==8.0.1; python_version >= "3.6"
cligj==0.7.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.6"
colorama==0.4.4; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" and sys_platform == "win32" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0" and sys_platform == "win32"
colorama==0.4.4; platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.6.2" and sys_platform == "win32" and python_version < "4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0")
configparser==5.0.2; python_version >= "3.6"
cycler==0.10.0; python_version >= "3.7"
debugpy==1.4.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
decorator==5.0.9; python_version >= "3.7"
defusedxml==0.7.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
distlib==0.3.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
dparse==0.5.1; python_version >= "3.5"
dynaconf==3.1.4
entrypoints==0.3; python_version >= "3.7"
et-xmlfile==1.1.0; python_version >= "3.6"
filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
fiona==1.8.20; python_version >= "3.6"
flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
geopandas==0.9.0; python_version >= "3.6"
idna==3.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.5"
importlib-metadata==4.6.3; python_version == "3.7"
importlib-metadata==4.6.3; python_version == "3.7" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6") and python_full_version >= "3.6.2" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version >= "3.6" and python_version < "3.8")
iniconfig==1.1.1; python_version >= "3.6"
ipykernel==6.1.0; python_version >= "3.7"
ipython-genutils==0.2.0; python_version >= "3.7"
ipython==7.26.0; python_version >= "3.7"
ipywidgets==7.6.3
isort==5.9.3; python_full_version >= "3.6.1" and python_version < "4.0" and python_version >= "3.6"
jedi==0.18.0; python_version >= "3.7"
jellyfish==0.6.1
jinja2==3.0.1; python_version >= "3.7"
@ -43,58 +56,80 @@ jupyter==1.0.0
jupyterlab-pygments==0.1.2; python_version >= "3.7"
jupyterlab-widgets==1.0.0; python_version >= "3.6"
kiwisolver==1.3.1; python_version >= "3.7"
lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0"
liccheck==0.6.2; python_version >= "2.7"
lxml==4.6.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
markupsafe==2.0.1; python_version >= "3.7"
matplotlib-inline==0.1.2; python_version >= "3.7"
matplotlib==3.4.3; python_version >= "3.7"
mccabe==0.6.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
mistune==0.8.4; python_version >= "3.7"
munch==2.5.0; python_version >= "3.6"
mypy-extensions==0.4.3; python_full_version >= "3.6.2" and python_version >= "3.5"
mypy==0.910; python_version >= "3.5"
nbclient==0.5.3; python_full_version >= "3.6.1" and python_version >= "3.7"
nbconvert==6.1.0; python_version >= "3.7"
nbformat==5.1.3; python_full_version >= "3.6.1" and python_version >= "3.7"
nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.7"
notebook==6.4.3; python_version >= "3.6"
numpy==1.21.1; python_version >= "3.7"
packaging==21.0; python_version >= "3.7"
openpyxl==3.0.7; python_version >= "3.6"
packaging==21.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
pandas==1.3.1; python_full_version >= "3.7.1"
pandocfilters==1.4.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
parso==0.8.2; python_version >= "3.7"
pathspec==0.9.0; python_full_version >= "3.6.2"
pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.7"
pickleshare==0.7.5; python_version >= "3.7"
pillow==8.3.1; python_version >= "3.7"
platformdirs==2.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
prometheus-client==0.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
prompt-toolkit==3.0.19; python_full_version >= "3.6.1" and python_version >= "3.7"
ptyprocess==0.7.0; sys_platform != "win32" and python_version >= "3.7" and os_name != "nt"
py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or implementation_name == "pypy" and python_version >= "3.6" and python_full_version >= "3.4.0"
py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or python_full_version >= "3.5.0" and python_version >= "3.6" and implementation_name == "pypy"
pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pygments==2.9.0; python_version >= "3.7"
pylint==2.9.6; python_version >= "3.6" and python_version < "4.0"
pypandoc==1.6.3
pyparsing==2.4.7; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7"
pyproj==3.1.0; python_version >= "3.7"
pyrsistent==0.18.0; python_version >= "3.6"
pytest==6.2.4; python_version >= "3.6"
python-dateutil==2.8.2; python_full_version >= "3.7.1" and python_version >= "3.7"
pytz==2021.1; python_full_version >= "3.7.1" and python_version >= "2.7"
pywin32==301; sys_platform == "win32" and python_version >= "3.6"
pywinpty==1.1.3; os_name == "nt" and python_version >= "3.6"
pyyaml==5.4.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0"
pyyaml==5.4.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.5"
pyzmq==22.2.1; python_full_version >= "3.6.1" and python_version >= "3.7"
qtconsole==5.1.1; python_version >= "3.6"
qtpy==1.9.0; python_version >= "3.6"
regex==2021.8.3; python_full_version >= "3.6.2"
requests==2.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
safety==1.10.3; python_version >= "3.5"
semantic-version==2.8.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "2.7"
send2trash==1.8.0; python_version >= "3.6"
shapely==1.7.1; python_version >= "3.6"
six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7"
six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
terminado==0.11.0; python_version >= "3.6"
testpath==0.5.0; python_version >= "3.7"
toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4.0"
tomli==1.2.1; python_version >= "3.6" and python_full_version >= "3.6.2"
tornado==6.1; python_full_version >= "3.6.1" and python_version >= "3.7"
tox==3.24.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
tqdm==4.62.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
traitlets==5.0.5; python_full_version >= "3.6.1" and python_version >= "3.7"
typed-ast==1.4.3; python_version < "3.8" and python_full_version >= "3.6.2" and python_version >= "3.6" and implementation_name == "cpython"
types-requests==2.25.6
typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6"
typing-extensions==3.10.0.0; python_version < "3.8" and python_full_version >= "3.6.2" and python_version >= "3.6"
urllib3==1.26.6; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "2.7"
us==2.0.2
virtualenv==20.7.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
wcwidth==0.2.5; python_full_version >= "3.6.1" and python_version >= "3.7"
webencodings==0.5.1; python_version >= "3.7"
widgetsnbextension==3.5.1
wrapt==1.12.1; python_version >= "3.6" and python_version < "4.0"
xlsxwriter==2.0.0
zipp==3.5.0; python_version < "3.8" and python_version >= "3.6"