From 369d0a461b34fbd850bbb0c80a94d615b5db6b8d Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 5 Aug 2021 14:34:27 -0700 Subject: [PATCH 01/12] remote: Refactor common remote path/URL parsing into a shared function Consolidation before I'm about to make changes to these bits of code. --- nextstrain/cli/command/remote/delete.py | 18 ++------------ nextstrain/cli/command/remote/download.py | 18 ++------------ nextstrain/cli/command/remote/ls.py | 19 ++------------ nextstrain/cli/command/remote/upload.py | 20 +++------------ nextstrain/cli/remote/__init__.py | 30 +++++++++++++++++++++++ 5 files changed, 39 insertions(+), 66 deletions(-) diff --git a/nextstrain/cli/command/remote/delete.py b/nextstrain/cli/command/remote/delete.py index 1172f933..8d2bbc4b 100644 --- a/nextstrain/cli/command/remote/delete.py +++ b/nextstrain/cli/command/remote/delete.py @@ -4,16 +4,10 @@ See `nextstrain remote --help` for more information on remote sources. """ -from urllib.parse import urlparse -from ...remote import s3 +from ...remote import parse_remote_path from ...util import warn -SUPPORTED_SCHEMES = { - "s3": s3, -} - - def register_parser(subparser): parser = subparser.add_parser( "delete", @@ -34,15 +28,7 @@ def register_parser(subparser): def run(opts): - url = urlparse(opts.remote_path) - - if url.scheme not in SUPPORTED_SCHEMES: - warn("Error: Unsupported remote scheme %s://" % url.scheme) - warn("") - warn("Supported schemes are: %s" % ", ".join(SUPPORTED_SCHEMES)) - return 1 - - remote = SUPPORTED_SCHEMES[url.scheme] + remote, url = parse_remote_path(opts.remote_path) deleted = remote.delete(url, recursively = opts.recursively) deleted_count = 0 diff --git a/nextstrain/cli/command/remote/download.py b/nextstrain/cli/command/remote/download.py index 85d766dd..7d362c0e 100644 --- a/nextstrain/cli/command/remote/download.py +++ b/nextstrain/cli/command/remote/download.py @@ -18,16 +18,10 @@ """ from pathlib import Path -from urllib.parse import urlparse -from ...remote import s3 +from ...remote import parse_remote_path from ...util import warn -SUPPORTED_SCHEMES = { - "s3": s3, -} - - def register_parser(subparser): parser = subparser.add_parser("download", help = "Download dataset and narrative files") @@ -54,15 +48,7 @@ def register_parser(subparser): def run(opts): - url = urlparse(opts.remote_path) - - if url.scheme not in SUPPORTED_SCHEMES: - warn("Error: Unsupported remote scheme %s://" % url.scheme) - warn("") - warn("Supported schemes are: %s" % ", ".join(SUPPORTED_SCHEMES)) - return 1 - - remote = SUPPORTED_SCHEMES[url.scheme] + remote, url = parse_remote_path(opts.remote_path) if opts.recursively and not opts.local_path.is_dir(): warn("Local path must be a directory when using --recursively; «%s» is not" % opts.local_path) diff --git a/nextstrain/cli/command/remote/ls.py b/nextstrain/cli/command/remote/ls.py index 1a102117..77e4dfeb 100644 --- a/nextstrain/cli/command/remote/ls.py +++ b/nextstrain/cli/command/remote/ls.py @@ -10,14 +10,7 @@ See `nextstrain remote --help` for more information on remote sources. """ -from urllib.parse import urlparse -from ...remote import s3 -from ...util import warn - - -SUPPORTED_SCHEMES = { - "s3": s3, -} +from ...remote import parse_remote_path def register_parser(subparser): @@ -35,15 +28,7 @@ def register_parser(subparser): def run(opts): - url = urlparse(opts.remote_path) - - if url.scheme not in SUPPORTED_SCHEMES: - warn("Error: Unsupported remote scheme %s://" % url.scheme) - warn("") - warn("Supported schemes are: %s" % ", ".join(SUPPORTED_SCHEMES)) - return 1 - - remote = SUPPORTED_SCHEMES[url.scheme] + remote, url = parse_remote_path(opts.remote_path) files = remote.ls(url) diff --git a/nextstrain/cli/command/remote/upload.py b/nextstrain/cli/command/remote/upload.py index 9470a0c3..1cf89374 100644 --- a/nextstrain/cli/command/remote/upload.py +++ b/nextstrain/cli/command/remote/upload.py @@ -13,14 +13,7 @@ """ from pathlib import Path -from urllib.parse import urlparse -from ...remote import s3 -from ...util import warn - - -SUPPORTED_SCHEMES = { - "s3": s3, -} +from ...remote import parse_remote_path def register_parser(subparser): @@ -47,16 +40,9 @@ def register_arguments(parser): def run(opts): - url = urlparse(opts.destination) - - if url.scheme not in SUPPORTED_SCHEMES: - warn("Error: Unsupported destination scheme %s://" % url.scheme) - warn("") - warn("Supported schemes are: %s" % ", ".join(SUPPORTED_SCHEMES)) - return 1 + remote, url = parse_remote_path(opts.destination) - remote = SUPPORTED_SCHEMES[url.scheme] - files = [Path(f) for f in opts.files] + files = [Path(f) for f in opts.files] uploads = remote.upload(url, files) diff --git a/nextstrain/cli/remote/__init__.py b/nextstrain/cli/remote/__init__.py index e69de29b..14bccf4e 100644 --- a/nextstrain/cli/remote/__init__.py +++ b/nextstrain/cli/remote/__init__.py @@ -0,0 +1,30 @@ +""" +Remote destinations and sources for Nextstrain datasets and narratives. +""" + +from textwrap import dedent +from types import ModuleType +from typing import Tuple +from urllib.parse import urlparse, ParseResult +from ..errors import UserError +from . import s3 + + +SUPPORTED_SCHEMES = { + "s3": s3, +} + + +def parse_remote_path(path: str) -> Tuple[ModuleType, ParseResult]: + url = urlparse(path) + + if url.scheme not in SUPPORTED_SCHEMES: + raise UserError(dedent(f"""\ + Unsupported remote scheme {url.scheme}:// + + Supported schemes are: {", ".join(SUPPORTED_SCHEMES)} + """)) + + remote = SUPPORTED_SCHEMES[url.scheme] + + return remote, url From 06d132b9767b3d026a63a0de3a88e9153d464c85 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 5 Aug 2021 15:31:05 -0700 Subject: [PATCH 02/12] errors: Make it easier to use """multi-line strings""" with UserError Standardize cleanup of multi-line strings passed to the UserError constructor in order to require less work by the callers. The easier it is to produce nice error messages, the more likely we are to write nice error messages. --- nextstrain/cli/command/login.py | 5 ++--- nextstrain/cli/errors.py | 8 +++++++- nextstrain/cli/remote/__init__.py | 5 ++--- nextstrain/cli/remote/s3.py | 13 ++++++------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/nextstrain/cli/command/login.py b/nextstrain/cli/command/login.py index c288c2b7..31dbced9 100644 --- a/nextstrain/cli/command/login.py +++ b/nextstrain/cli/command/login.py @@ -20,7 +20,6 @@ from functools import partial from getpass import getpass from os import environ -from textwrap import dedent from ..authn import current_user, login from ..errors import UserError @@ -79,11 +78,11 @@ def run(opts): print() else: if opts.username is not None and opts.username != user.username: - raise UserError(dedent(f"""\ + raise UserError(f""" Login requested for {opts.username}, but {user.username} is already logged in. Please logout first if you want to switch users. - """).rstrip()) + """) print(f"Logged into nextstrain.org as {user.username}.") print("Log out with `nextstrain logout`.") diff --git a/nextstrain/cli/errors.py b/nextstrain/cli/errors.py index ed41beb6..264ec89c 100644 --- a/nextstrain/cli/errors.py +++ b/nextstrain/cli/errors.py @@ -1,6 +1,8 @@ """ Exception classes for internal use. """ +from textwrap import dedent + class NextstrainCliError(Exception): """Exception base class for all custom :mod:`nextstrain.cli` exceptions.""" @@ -8,4 +10,8 @@ class NextstrainCliError(Exception): class UserError(NextstrainCliError): def __init__(self, message, *args, **kwargs): - super().__init__("Error: " + message, *args, **kwargs) + # Remove leading newlines, trailing whitespace, and then indentation + # to better support nicely-formatted """multi-line strings""". + formatted_message = dedent(message.lstrip("\n").rstrip()) + + super().__init__("Error: " + formatted_message, *args, **kwargs) diff --git a/nextstrain/cli/remote/__init__.py b/nextstrain/cli/remote/__init__.py index 14bccf4e..8805623b 100644 --- a/nextstrain/cli/remote/__init__.py +++ b/nextstrain/cli/remote/__init__.py @@ -2,7 +2,6 @@ Remote destinations and sources for Nextstrain datasets and narratives. """ -from textwrap import dedent from types import ModuleType from typing import Tuple from urllib.parse import urlparse, ParseResult @@ -19,11 +18,11 @@ def parse_remote_path(path: str) -> Tuple[ModuleType, ParseResult]: url = urlparse(path) if url.scheme not in SUPPORTED_SCHEMES: - raise UserError(dedent(f"""\ + raise UserError(f""" Unsupported remote scheme {url.scheme}:// Supported schemes are: {", ".join(SUPPORTED_SCHEMES)} - """)) + """) remote = SUPPORTED_SCHEMES[url.scheme] diff --git a/nextstrain/cli/remote/s3.py b/nextstrain/cli/remote/s3.py index 8d597884..c73f91d7 100644 --- a/nextstrain/cli/remote/s3.py +++ b/nextstrain/cli/remote/s3.py @@ -12,7 +12,6 @@ from operator import methodcaller from os.path import commonprefix from pathlib import Path -from textwrap import dedent from time import time from typing import Iterable, List, Tuple from .. import aws @@ -68,11 +67,11 @@ def download(url: urllib.parse.ParseResult, local_path: Path, recursively: bool objects = [ item.Object() for item in bucket.objects.filter(Prefix = path) ] else: if not path: - raise UserError(dedent("""\ - No file path specified in URL (%s); nothing to download. + raise UserError(f""" + No file path specified in URL ({url.geturl()!s}); nothing to download. Did you mean to use --recursively? - """ % (str(url.geturl())))) + """) object = bucket.Object(path) assert_exists(object) @@ -161,11 +160,11 @@ def split_url(url: urllib.parse.ParseResult) -> Tuple[S3Bucket, str]: boto3.client("s3").head_bucket(Bucket = bucket.name) except ClientError as error: - raise UserError(dedent('''\ - No bucket exists with the name "%s". + raise UserError(f""" + No bucket exists with the name "{bucket.name}". Buckets are not automatically created for safety reasons. - ''' % bucket.name)) + """) return bucket, prefix From ac45e4df73464e1939883a9bdee04a64edb653b4 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 12:40:15 -0700 Subject: [PATCH 03/12] dev: Ignore .venv directories when running flake8 Otherwise it will want to scan a few thousand files instead of a few dozen. --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 59e6f506..75c8d610 100644 --- a/.flake8 +++ b/.flake8 @@ -10,6 +10,7 @@ select = exclude = .git, + .venv, __pycache__, build, dist, From 1a04901b4aee824e995e0427402d387e2b476e96 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 12:52:23 -0700 Subject: [PATCH 04/12] dev: Ignore a deprecation warning generated by flake8's usage of importlib_metadata The warning was turned into an error by our pytest config, and thus resulted in test failures. importlib_metadata used to specially suppress this deprecation warning for flake8, but that special-casing was removed in June without any update to flake8's to avoid the warning. See https://github.com/python/importlib_metadata/pull/319 and the flake8 PRs it links to for more background. --- pytest.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index e9fffce6..3693332d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,6 +9,7 @@ python_functions = pytest_* # Turn warnings into errors. Warnings are signs that something needs # attention, even if that means choosing to add an explicit ignore filter rule -# for them. +# for them. Last matching filter wins. filterwarnings = error + ignore:SelectableGroups dict interface:DeprecationWarning:flake8 From 2131f8cbab40dae2c133537dac8d78034be0f6e8 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 12:10:19 -0700 Subject: [PATCH 05/12] dev: Add pyproject.toml explicitly declaring we use setuptools setuptools is still Pip's default (and maybe always will be?), but it's good to be explicit. More toolchain config will likely be added to this file in the future. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..9787c3bd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" From 0f55f978b6e979cb228c5e5a72f47c20854ce9f4 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 5 Aug 2021 15:07:30 -0700 Subject: [PATCH 06/12] dev: Add extra type annotation packages required by newer Mypy versions --- Pipfile.lock | 613 +++++++++++++++++++++++++++------------------------ setup.py | 2 + 2 files changed, 321 insertions(+), 294 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index b6af5d7e..ecc69feb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -21,10 +21,10 @@ "boto3" ], "hashes": [ - "sha256:58cc422e65fc89f7cb78eca740d241ac8e15f39f6b308cc23152711e8a987d45" + "sha256:b6bae95c55ef822d790bf8ebf6aed3d09b33e2817fa5f10e16a77028332963c2" ], "markers": "python_version >= '3.6'", - "version": "==1.2.1" + "version": "==1.3.3" }, "aiohttp": { "hashes": [ @@ -71,11 +71,11 @@ }, "aioitertools": { "hashes": [ - "sha256:54a56c7cf3b5290d1cb5e8974353c9f52c677612b5d69a859369a020c53414a3", - "sha256:8972308474c41ed5e0636819f948ebff32f2318e70f7e7d23cd208c4357cc773" + "sha256:3a141f01d1050ac8c01917aee248d262736dab875ce0471f0dba5f619346b452", + "sha256:8b02facfbc9b0f1867739949a223f3d3267ed8663691cc95abd94e2c1d8c2b46" ], "markers": "python_version >= '3.6'", - "version": "==0.7.1" + "version": "==0.8.0" }, "alabaster": { "hashes": [ @@ -94,82 +94,92 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "babel": { "hashes": [ - "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5", - "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05" + "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", + "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.0" + "version": "==2.9.1" }, "boto3": { "hashes": [ - "sha256:360a9f805b11f2e468d48815193c55278765fb30b64350893ab63236a5034726", - "sha256:81c514185de8937ba75023a2466fae0cc6f170e6348fdac31c235c32ba9d58f3" + "sha256:231b2023f4fe12af679afa7d893534ce2703db2318a8fa51fc7876890760f352", + "sha256:c0740378b913ca53f5fc0dba91e99a752c5a30ae7b58a0c5e54e3e2a68df26c5" ], - "version": "==1.16.52" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.17.106" }, "botocore": { "hashes": [ - "sha256:d8f50e4162012ccfab64c2db4fcc99313d46d57789072251bab56013d66546e2", - "sha256:dc5ec23deadbe9327d3c81d03fddf80805c549059baabd80dea605941fe6a221" + "sha256:47ec01b20c4bc6aaa16d21f756ead2f437b47c1335b083356cdc874e9140b023", + "sha256:6d5c983808b1d00437f56d0c08412bd82d9f8012fdb77e555f97277a1fd4d5df" ], - "version": "==1.19.52" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.20.106" }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" ], - "version": "==2020.12.5" + "version": "==2021.5.30" }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" - ], - "version": "==1.14.5" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "version": "==1.14.6" }, "chardet": { "hashes": [ @@ -179,6 +189,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, + "charset-normalizer": { + "hashes": [ + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" + ], + "markers": "python_version >= '3'", + "version": "==2.0.4" + }, "commonmark": { "hashes": [ "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", @@ -188,20 +206,20 @@ }, "cryptography": { "hashes": [ - "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b", - "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336", - "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87", - "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7", - "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799", - "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b", - "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df", - "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0", - "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3", - "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724", - "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2", - "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964" - ], - "version": "==3.4.6" + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + ], + "version": "==3.4.7" }, "docutils": { "hashes": [ @@ -214,10 +232,10 @@ }, "fasteners": { "hashes": [ - "sha256:74b6847e0a6bb3b56c8511af8e24c40e4cf7a774dfff5b251c260ed338096a4b", - "sha256:c995d8c26b017c5d6a6de9ad29a0f9cdd57de61ae1113d28fac26622b06a0933" + "sha256:8408e52656455977053871990bd25824d85803b9417aa348f10ba29ef0c751f7", + "sha256:b1ab4e5adfbc28681ce44b3024421c4f567e705cc3963c732bf1cba3348307de" ], - "version": "==0.16" + "version": "==0.16.3" }, "filelock": { "hashes": [ @@ -228,26 +246,27 @@ }, "flake8": { "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", + "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.8.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.9.2" }, "fsspec": { "hashes": [ - "sha256:4b11557a90ac637089b10afa4c77adf42080c0696f6f2771c41ce92d73c41432", - "sha256:65dbf8244a3a3d23342109925f9f588c7551b2b01a5f47e555043b17e2b32d62" + "sha256:792ebd3b54de0b30f1ce73f0ba0a8bcc864724f2d9f248cb8d0ece47db0cbde8", + "sha256:86822ccf367da99957f49db64f7d5fd3d8d21444fac4dfdc8ebc38ee93d478c6" ], - "version": "==0.8.7" + "markers": "python_version >= '3.6'", + "version": "==2021.7.0" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "idna-ssl": { "hashes": [ @@ -266,11 +285,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:18d5ff601069f98d5d605b6a4b50c18a34811d655c55548adc833e687289acde", - "sha256:407d13f55dc6f2a844e62325d18ad7019a436c4bfcaee34cda35f2be6e7c3e34" + "sha256:0645585859e9a6689c523927a5032f2ba5919f1f7d0e84bd4533312320de1ff9", + "sha256:51c6635429c77cf1ae634c997ff9e53ca3438b495f10a55ba28594dd69764a8b" ], "markers": "python_version < '3.8'", - "version": "==3.7.2" + "version": "==4.6.3" }, "iniconfig": { "hashes": [ @@ -281,11 +300,11 @@ }, "jinja2": { "hashes": [ - "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", - "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.3" + "markers": "python_version >= '3.6'", + "version": "==3.0.1" }, "jmespath": { "hashes": [ @@ -305,61 +324,43 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", - "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", - "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", - "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", - "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", - "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", - "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", - "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", - "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "mccabe": { "hashes": [ @@ -413,31 +414,32 @@ }, "mypy": { "hashes": [ - "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e", - "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064", - "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c", - "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4", - "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97", - "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df", - "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8", - "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a", - "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56", - "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7", - "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6", - "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5", - "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a", - "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521", - "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564", - "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49", - "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66", - "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a", - "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119", - "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506", - "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c", - "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb" + "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", + "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", + "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", + "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", + "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", + "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", + "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", + "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", + "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", + "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", + "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", + "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", + "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", + "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", + "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", + "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", + "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", + "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", + "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", + "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", + "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", + "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", + "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" ], "markers": "python_version < '3.8'", - "version": "==0.812" + "version": "==0.910" }, "mypy-extensions": { "hashes": [ @@ -448,30 +450,38 @@ }, "netifaces": { "hashes": [ - "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215", - "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b", - "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3", - "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa", - "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c", - "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084", - "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89", - "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994", - "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2", - "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae", - "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe", - "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc", - "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24", - "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42", - "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc", - "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29", - "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea", - "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1", - "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940", - "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7", - "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b", - "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b" - ], - "version": "==0.10.9" + "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32", + "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea", + "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85", + "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5", + "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5", + "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7", + "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0", + "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c", + "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05", + "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9", + "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b", + "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff", + "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d", + "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4", + "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4", + "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1", + "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4", + "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f", + "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246", + "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150", + "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3", + "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be", + "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89", + "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1", + "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4", + "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac", + "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8", + "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048", + "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1", + "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1" + ], + "version": "==0.11.0" }, "nextstrain-cli": { "editable": true, @@ -482,18 +492,18 @@ }, "nextstrain-sphinx-theme": { "hashes": [ - "sha256:27947c09cdfa2c7f5f9ac680082220571e720705a4712c9e6f78420be0445e6c", - "sha256:7cbc6f0eb1642f15da646c29e8a18a558b02227c520d1e043265e84916636daf" + "sha256:1d5f7a31b3da75bad143f26257a140c77d2c2641dcf223b2c5053bda8c6d39ea", + "sha256:b6a021449f1ed3ffd9ae2cb437d1f6135a12b8a0d06c329451b81cfc071e1ce1" ], - "version": "==2020.6" + "version": "==2021.1" }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" + "markers": "python_version >= '3.6'", + "version": "==21.0" }, "pluggy": { "hashes": [ @@ -513,11 +523,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" + "version": "==2.7.0" }, "pycparser": { "hashes": [ @@ -529,30 +539,30 @@ }, "pyflakes": { "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" + "version": "==2.3.1" }, "pygments": { "hashes": [ - "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94", - "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8" + "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", + "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" ], "markers": "python_version >= '3.5'", - "version": "==2.8.1" + "version": "==2.9.0" }, "pyjwt": { "extras": [ "crypto" ], "hashes": [ - "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7", - "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847" + "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1", + "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130" ], "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "version": "==2.1.0" }, "pyparsing": { "hashes": [ @@ -564,11 +574,11 @@ }, "pytest": { "hashes": [ - "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9", - "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839" + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "markers": "python_version >= '3.6'", - "version": "==6.2.2" + "version": "==6.2.4" }, "pytest-flake8": { "hashes": [ @@ -579,19 +589,19 @@ }, "pytest-mypy": { "hashes": [ - "sha256:63d418a4fea7d598ac40b659723c00804d16a251d90a5cfbca213eeba5aaf01c", - "sha256:8d2112972c1debf087943f48963a0daf04f3424840aea0cf437cc97053b1b0ef" + "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11", + "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b" ], "markers": "python_version >= '3.5'", - "version": "==0.8.0" + "version": "==0.8.1" }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.1" + "version": "==2.8.2" }, "pytz": { "hashes": [ @@ -609,34 +619,34 @@ }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.25.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" }, "s3fs": { "hashes": [ - "sha256:7396943cbc1cf92eb6f7aa93be5f64a3bfa59d76908262e89bae06e3c87fa59d", - "sha256:b9415e12e14fab44a67e505017b128ac6a5e63204d74d6622100ae10c764172e" + "sha256:293294ec8ed08605617db440e3a50229a413dc16dcf32c948fae8cbd9b02ae96", + "sha256:6b1699ef3477a51dd95ea3ccc8210af85cf81c27ad56aab13deda1ae7d6670a5" ], "markers": "python_version >= '3.6'", - "version": "==0.5.1" + "version": "==2021.7.0" }, "s3transfer": { "hashes": [ - "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed", - "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2" + "sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc", + "sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2" ], - "version": "==0.3.4" + "version": "==0.4.2" }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" + "version": "==1.16.0" }, "snowballstemmer": { "hashes": [ @@ -647,11 +657,11 @@ }, "sphinx": { "hashes": [ - "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff", - "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac" + "sha256:3092d929cd807926d846018f2ace47ba2f3b671b309c7a89cd3306e80c826b13", + "sha256:46d52c6cee13fec44744b8c01ed692c18a640f6910a725cbb938bc36e8d64544" ], - "markers": "python_version >= '3.5'", - "version": "==3.5.2" + "markers": "python_version >= '3.6'", + "version": "==4.1.2" }, "sphinx-argparse": { "file": "https://github.com/alex-rudakov/sphinx-argparse/archive/b3b649743d4f8854349a2416ccdd770f41739c35.tar.gz", @@ -669,10 +679,10 @@ }, "sphinx-rtd-theme": { "hashes": [ - "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5", - "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113" + "sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a", + "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f" ], - "version": "==0.5.1" + "version": "==0.5.2" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -692,11 +702,11 @@ }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", - "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" + "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", + "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" + "markers": "python_version >= '3.6'", + "version": "==2.0.0" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -716,11 +726,11 @@ }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", - "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" + "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", + "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" ], "markers": "python_version >= '3.5'", - "version": "==1.1.4" + "version": "==1.1.5" }, "toml": { "hashes": [ @@ -732,55 +742,70 @@ }, "typed-ast": { "hashes": [ - "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", - "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", - "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", - "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", - "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", - "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", - "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", - "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", - "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", - "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", - "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", - "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", - "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", - "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", - "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", - "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", - "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", - "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", - "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", - "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", - "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", - "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", - "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", - "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", - "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", - "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", - "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", - "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", - "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", - "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" - ], - "version": "==1.4.2" + "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace", + "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff", + "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266", + "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528", + "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6", + "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808", + "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4", + "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363", + "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341", + "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04", + "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41", + "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e", + "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3", + "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899", + "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805", + "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c", + "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c", + "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39", + "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a", + "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3", + "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7", + "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f", + "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075", + "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0", + "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40", + "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428", + "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927", + "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3", + "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f", + "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65" + ], + "markers": "python_version < '3.8'", + "version": "==1.4.3" + }, + "types-requests": { + "hashes": [ + "sha256:03122b582f5300ec35ac6692f2634207c467e602dc9ba46b5811a9f6ce0b0bc2", + "sha256:a4c03c654527957a70002079ca48669b53d82eac4811abf140ea93847b65529b" + ], + "version": "==2.25.2" + }, + "types-setuptools": { + "hashes": [ + "sha256:71ed0f4c71d8fb5f3026a90ae82d163c13749b110e157d82126725ac8f714360", + "sha256:b3ada82b21dcb8e0cafd7658d8a16018a000e55bdb7f6f3cec033223360563ce" + ], + "version": "==57.0.0" }, "typing-extensions": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], "markers": "python_version < '3.8'", - "version": "==3.7.4.3" + "version": "==3.10.0.0" }, "urllib3": { "hashes": [ - "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", - "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], - "markers": "python_version != '3.4'", - "version": "==1.26.3" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.6" }, "wrapt": { "hashes": [ @@ -833,11 +858,11 @@ }, "zipp": { "hashes": [ - "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", - "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3", + "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" ], "markers": "python_version >= '3.6'", - "version": "==3.4.1" + "version": "==3.5.0" } }, "develop": {} diff --git a/setup.py b/setup.py index 588f5099..651d294f 100644 --- a/setup.py +++ b/setup.py @@ -128,6 +128,8 @@ def find_namespaced_packages(namespace): "sphinx-argparse", "sphinx-markdown-tables", "sphinx_rtd_theme", + "types-setuptools", + "types-requests", ], }, ) From e0faaf52231bf3a388edbaa23332df87e14ec631 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 16:39:40 -0700 Subject: [PATCH 07/12] dev: Add (optional) type checking with Pyright to our tests Pyright supports some checks and type inference that Mypy does not. In particular, it allows modules to be implementations of Protocols. As Mypy also supports some features that Pyright does not, we'll just use both. However, Pyright is written in TypeScript so cannot be declared as a normal development dependency. To use it, it must be manually installed as `pyright` (e.g. with `npm install -g` or `snap`) or `npx` must be available to run it on-demand. The latter is usually easiest, provided you have a new-enough version of Node already installed (>=12). The automated test is skipped if neither `pyright` nor `npx` are available. The Pyright configuration here is stricter than the default level ("basic") but more permissive than the optional "strict" level. I enabled what I could cleanly, without requiring large code changes. It's easier to enable stricter rules early, before future code is written, than trying to do so later and fixing a lot of retroactive infractions. pyright-status: FAILING mypy-status: PASSING --- .github/workflows/ci.yaml | 4 ++-- doc/development.md | 12 +++++++++++- pyrightconfig.json | 20 ++++++++++++++++++++ pytest.ini | 2 +- tests/pyright.py | 19 +++++++++++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 pyrightconfig.json create mode 100644 tests/pyright.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e9ad7ef5..39a0866b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,11 +31,11 @@ jobs: - name: Setup test environment run: | # Install required external programs for --native. - conda create -p ~/ci -c bioconda \ + conda create -p ~/ci -c conda-forge -c bioconda \ fasttree \ iqtree \ mafft \ - nodejs=10 \ + nodejs=12 \ raxml \ vcftools diff --git a/doc/development.md b/doc/development.md index 81043cb6..48672936 100644 --- a/doc/development.md +++ b/doc/development.md @@ -67,7 +67,16 @@ During development you can run static type checks using [mypy][]: $ mypy nextstrain # No output is good! -There are also many [editor integrations for mypy][]. +and [pyright][]: + + $ npx pyright + ... + Found 40 source files + 0 errors, 0 warnings, 0 infos + Completed in 2sec + +There are also many [editor integrations for mypy][], and Pyright is integrated +into VS Code's Python support. The [`typing_extensions`][] module should be used for features added to the standard `typings` module after 3.6. (Currently this isn't necessary since we @@ -86,6 +95,7 @@ safety and correctness. You can run them like this: [twine]: https://pypi.org/project/twine [type annotations]: https://www.python.org/dev/peps/pep-0484/ [mypy]: http://mypy-lang.org/ +[pyright]: https://github.com/microsoft/pyright [editor integrations for mypy]: https://github.com/python/mypy#ide--linter-integrations [`typing_extensions`]: https://pypi.org/project/typing-extensions [Flake8]: https://flake8.pycqa.org diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..9230cead --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,20 @@ +{ + "pythonVersion": "3.6", + "include": ["nextstrain"], + "reportMissingImports": false, + "reportMissingModuleSource": true, + "reportUnusedFunction": true, + "reportWildcardImportFromLibrary": true, + "reportUntypedFunctionDecorator": true, + "reportUntypedClassDecorator": true, + "reportUntypedBaseClass": true, + "reportUntypedNamedTuple": true, + "reportPrivateUsage": true, + "reportInvalidStringEscapeSequence": true, + "reportInvalidTypeVarUse": true, + "reportAssertAlwaysTrue": true, + "reportSelfClsParameterName": true, + "reportInvalidStubStatement": true, + "reportIncompleteStub": true, + "reportUnsupportedDunderAll": true +} diff --git a/pytest.ini b/pytest.ini index 3693332d..e5044c43 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] addopts = --mypy --flake8 --doctest-modules -testpaths = nextstrain +testpaths = nextstrain tests python_files = *.py # Avoid catching test_setup() functions. I've always disliked the name-based diff --git a/tests/pyright.py b/tests/pyright.py new file mode 100644 index 00000000..606e0e15 --- /dev/null +++ b/tests/pyright.py @@ -0,0 +1,19 @@ +import pytest +from pathlib import Path +from shutil import which +from subprocess import run + +topdir = Path(__file__).resolve().parent.parent + +if which("pyright"): + pyright = ["pyright"] +elif which("npx"): + pyright = ["npx", "pyright"] +else: + pyright = None + +@pytest.mark.skipif(not pyright, reason = "pyright is not available") +def pytest_pyright(): + # Check the exit status ourselves for nicer test output on failure + result = run(pyright, cwd = topdir) + assert result.returncode == 0, "pyright exited with errors" From a802e6714217ea06f30e53e80e9055c79609c61d Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 20:43:59 -0700 Subject: [PATCH 08/12] dev: Address most issues flagged by Pyright Excludes the flagged issues related to ModuleType, which require a bit more change. I think they will be best addressed in a separate, following commit. This commit mostly adds type guards to refine the type inference. The upshot of the guards is that they'll explicitly catch violations/bugs at runtime in more obvious ways than if they weren't present. The remaining changes in the commit improve the annotations themselves. pyright-status: FAILING mypy-status: PASSING --- nextstrain/cli/argparse.py | 2 +- nextstrain/cli/authn.py | 4 ++++ nextstrain/cli/gzip.py | 4 ++++ nextstrain/cli/runner/aws_batch/logs.py | 6 +++--- nextstrain/cli/util.py | 1 + nextstrain/cli/volume.py | 8 +++++--- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/nextstrain/cli/argparse.py b/nextstrain/cli/argparse.py index 8c9dbd0a..fcdb75de 100644 --- a/nextstrain/cli/argparse.py +++ b/nextstrain/cli/argparse.py @@ -145,7 +145,7 @@ class AppendOverwriteDefault(Action): def __call__(self, parser, namespace, value, option_string = None): current = getattr(namespace, self.dest, None) - if current is parser.get_default(self.dest): + if current is parser.get_default(self.dest) or current is None: current = [] setattr(namespace, self.dest, [*current, value]) diff --git a/nextstrain/cli/authn.py b/nextstrain/cli/authn.py index 01303a47..a5628359 100644 --- a/nextstrain/cli/authn.py +++ b/nextstrain/cli/authn.py @@ -56,6 +56,8 @@ def login(username: str, password: str) -> User: _save_tokens(session) print(f"Credentials saved to {config.SECRETS}.") + assert session.id_claims + return User(session.id_claims) @@ -99,6 +101,8 @@ def current_user() -> Optional[User]: except (cognito.TokenError, cognito.NotAuthorizedError): return None + assert session.id_claims + return User(session.id_claims) diff --git a/nextstrain/cli/gzip.py b/nextstrain/cli/gzip.py index 1cd4db35..9e6bb5b7 100644 --- a/nextstrain/cli/gzip.py +++ b/nextstrain/cli/gzip.py @@ -32,6 +32,7 @@ def readable(self): def read(self, size = None): assert size != 0 + assert self.stream if size is None: size = -1 @@ -93,14 +94,17 @@ def writable(self): return True def write(self, data: bytes): # type: ignore[override] + assert self.stream and self.__gunzip return self.stream.write(self.__gunzip.decompress(data)) def flush(self): + assert self.stream and self.__gunzip super().flush() self.stream.flush() def close(self): if self.stream: + assert self.__gunzip try: self.stream.write(self.__gunzip.flush()) self.stream.close() diff --git a/nextstrain/cli/runner/aws_batch/logs.py b/nextstrain/cli/runner/aws_batch/logs.py index 889b0565..20981695 100644 --- a/nextstrain/cli/runner/aws_batch/logs.py +++ b/nextstrain/cli/runner/aws_batch/logs.py @@ -4,7 +4,7 @@ import threading from botocore.exceptions import ClientError, ConnectionError as BotocoreConnectionError -from typing import Callable, Generator, MutableSet +from typing import Any, Callable, Dict, Generator, MutableSet from ... import aws @@ -25,10 +25,10 @@ def fetch_stream(stream: str, start_time: int = None) -> Generator[dict, None, N log_events = client.get_paginator("filter_log_events") - query = { + query: Dict[str, Any] = { "logGroupName": LOG_GROUP, "logStreamNames": [ stream ], - } # type: dict + } if start_time: query["startTime"] = start_time diff --git a/nextstrain/cli/util.py b/nextstrain/cli/util.py index 8858d53b..3604419d 100644 --- a/nextstrain/cli/util.py +++ b/nextstrain/cli/util.py @@ -54,6 +54,7 @@ def check_for_new_version(): installed_into_user_site = \ site.ENABLE_USER_SITE \ + and site.USER_SITE is not None \ and __file__.startswith(site.USER_SITE) if sys.executable: diff --git a/nextstrain/cli/volume.py b/nextstrain/cli/volume.py index f326c040..0996b082 100644 --- a/nextstrain/cli/volume.py +++ b/nextstrain/cli/volume.py @@ -3,11 +3,13 @@ """ import argparse -from collections import namedtuple +from typing import NamedTuple from pathlib import Path -NamedVolume = namedtuple("NamedVolume", ("name", "src")) +class NamedVolume(NamedTuple): + name: str + src: Path def store_volume(volume_name): @@ -18,7 +20,7 @@ def store_volume(volume_name): Multiple argparse arguments can use this to cooperatively accept source path definitions for named volumes. - Each named volume is stored as a namedtuple (name, src). The tuple is + Each named volume is stored as a NamedTuple (name, src). The tuple is stored on the options object under the volume's name (modified to replace slashes with underscores), as well as added to a shared list of volumes, accessible via the "volumes" attribute on the options object. From 7cdd0d56a21cc67ae849cca0fb58e0ea81ad80fd Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 20:47:11 -0700 Subject: [PATCH 09/12] dev: Type check our runner.* and remote.* module interfaces with Protocols This closes a big hole in the codebase's type checking coverage and is the primary reason for introducing Pyright in the first place. The runner and remote interfaces are the two main adapter patterns used by this codebase. pyright-status: PASSING mypy-status: PASSING (but is not actually checking the Protocol) --- doc/development.md | 3 +- nextstrain/cli/command/check_setup.py | 2 +- nextstrain/cli/remote/__init__.py | 39 +++++++++++++--- nextstrain/cli/runner/__init__.py | 64 ++++++++++++++++++--------- nextstrain/cli/types.py | 46 ++++++++++++++++++- nextstrain/cli/util.py | 9 ++-- setup.py | 1 + 7 files changed, 128 insertions(+), 36 deletions(-) diff --git a/doc/development.md b/doc/development.md index 48672936..6f680e83 100644 --- a/doc/development.md +++ b/doc/development.md @@ -79,8 +79,7 @@ There are also many [editor integrations for mypy][], and Pyright is integrated into VS Code's Python support. The [`typing_extensions`][] module should be used for features added to the -standard `typings` module after 3.6. (Currently this isn't necessary since we -don't use those features.) +standard `typings` module after 3.6. We also use [Flake8][] for some static analysis checks focusing on runtime safety and correctness. You can run them like this: diff --git a/nextstrain/cli/command/check_setup.py b/nextstrain/cli/command/check_setup.py index 20fce53f..dfdd8a9d 100644 --- a/nextstrain/cli/command/check_setup.py +++ b/nextstrain/cli/command/check_setup.py @@ -61,7 +61,7 @@ def run(opts: Options) -> int: print("Testing your setup…") runner_tests = [ - (runner, runner.test_setup()) # type: ignore + (runner, runner.test_setup()) for runner in all_runners ] diff --git a/nextstrain/cli/remote/__init__.py b/nextstrain/cli/remote/__init__.py index 8805623b..0d81d78e 100644 --- a/nextstrain/cli/remote/__init__.py +++ b/nextstrain/cli/remote/__init__.py @@ -2,19 +2,44 @@ Remote destinations and sources for Nextstrain datasets and narratives. """ -from types import ModuleType -from typing import Tuple +from pathlib import Path +from typing import cast, Dict, Iterable, List, Tuple, TYPE_CHECKING from urllib.parse import urlparse, ParseResult from ..errors import UserError -from . import s3 - - -SUPPORTED_SCHEMES = { +from ..types import RemoteModule +from . import s3 as __s3 + + +# While PEP-0544 allows for modules to serve as implementations of Protocols¹, +# Mypy doesn't currently support it². Pyright does³, however, so we tell Mypy +# to "trust us", but let Pyright actually check our work. Mypy considers the +# MYPY variable to always be True when evaluating the code, regardless of the +# assignment below. +# +# This bit of type checking chicanery is not ideal, but the benefit of having +# our module interfaces actually checked by Pyright is worth it. In the +# future, we should maybe ditch Mypy in favor of Pyright alone, but I didn't +# want to put in the due diligence for a full switchover right now. +# +# -trs, 12 August 2021 +# +# ¹ https://www.python.org/dev/peps/pep-0544/#modules-as-implementations-of-protocols +# ² https://github.com/python/mypy/issues/5018 +# ³ https://github.com/microsoft/pyright/issues/1341 +# +MYPY = False +if TYPE_CHECKING and MYPY: + s3 = cast(RemoteModule, __s3) +else: + s3 = __s3 + + +SUPPORTED_SCHEMES: Dict[str, RemoteModule] = { "s3": s3, } -def parse_remote_path(path: str) -> Tuple[ModuleType, ParseResult]: +def parse_remote_path(path: str) -> Tuple[RemoteModule, ParseResult]: url = urlparse(path) if url.scheme not in SUPPORTED_SCHEMES: diff --git a/nextstrain/cli/runner/__init__.py b/nextstrain/cli/runner/__init__.py index c36b8c92..9b9f9ab7 100644 --- a/nextstrain/cli/runner/__init__.py +++ b/nextstrain/cli/runner/__init__.py @@ -1,14 +1,47 @@ import argparse from argparse import ArgumentParser from textwrap import dedent -from typing import Any, Mapping, List -from . import docker, native, aws_batch +from typing import cast, Mapping, List, TYPE_CHECKING +from . import ( + docker as __docker, + native as __native, + aws_batch as __aws_batch, +) from .. import config -from ..types import Options +from ..types import Options, RunnerModule from ..util import runner_name, runner_help, warn from ..volume import NamedVolume -all_runners = [ + +# While PEP-0544 allows for modules to serve as implementations of Protocols¹, +# Mypy doesn't currently support it². Pyright does³, however, so we tell Mypy +# to "trust us", but let Pyright actually check our work. Mypy considers the +# MYPY variable to always be True when evaluating the code, regardless of the +# assignment below. +# +# This bit of type checking chicanery is not ideal, but the benefit of having +# our module interfaces actually checked by Pyright is worth it. In the +# future, we should maybe ditch Mypy in favor of Pyright alone, but I didn't +# want to put in the due diligence for a full switchover right now. +# +# -trs, 12 August 2021 +# +# ¹ https://www.python.org/dev/peps/pep-0544/#modules-as-implementations-of-protocols +# ² https://github.com/python/mypy/issues/5018 +# ³ https://github.com/microsoft/pyright/issues/1341 +# +MYPY = False +if TYPE_CHECKING and MYPY: + docker = cast(RunnerModule, __docker) + native = cast(RunnerModule, __native) + aws_batch = cast(RunnerModule, __aws_batch) +else: + docker = __docker + native = __native + aws_batch = __aws_batch + + +all_runners: List[RunnerModule] = [ docker, native, aws_batch, @@ -27,21 +60,11 @@ % (configured_runner, runner_name(default_runner))) -# The types of "runners" and "default" are left vague because a generic -# parameterization isn't easily possible with default values, as reported -# https://github.com/python/mypy/issues/3737. The workaround becomes pretty -# sticky pretty quick for our use case, making it not worth it in my -# estimation. It'd make things more confusing rather than more clear. -# -# Additionally, there seems to be no way to use the structural/duck typing -# provided by the Protocol type to annotate a _module_ type with attributes -# instead of a _class_. Oh well. -# -trs, 15 August 2018 def register_runners(parser: ArgumentParser, exec: List, - runners: List = all_runners, - default: Any = default_runner) -> None: + runners: List[RunnerModule] = all_runners, + default: RunnerModule = default_runner) -> None: """ Register runner selection flags and runner-specific arguments on the given ArgumentParser instance. @@ -54,7 +77,7 @@ def register_runners(parser: ArgumentParser, register_arguments(parser, runners, exec = exec) -def register_flags(parser: ArgumentParser, runners: List, default: Any) -> None: +def register_flags(parser: ArgumentParser, runners: List[RunnerModule], default: RunnerModule) -> None: """ Register runner selection flags on the given ArgumentParser instance. """ @@ -91,7 +114,7 @@ def register_flags(parser: ArgumentParser, runners: List, default: Any) -> None: default = argparse.SUPPRESS) -def register_arguments(parser: ArgumentParser, runners: List, exec: List) -> None: +def register_arguments(parser: ArgumentParser, runners: List[RunnerModule], exec: List) -> None: """ Register arguments shared by all runners as well as runner-specific arguments on the given ArgumentParser instance. @@ -110,7 +133,7 @@ def register_arguments(parser: ArgumentParser, runners: List, exec: List) -> Non "--image", help = "Container image name to use for the Nextstrain computing environment", metavar = "", - default = docker.DEFAULT_IMAGE) + default = docker.DEFAULT_IMAGE) # type: ignore # Program to execute # @@ -164,7 +187,8 @@ def run(opts: Options, working_volume: NamedVolume = None, extra_env: Mapping = ) ] - if opts.image != docker.DEFAULT_IMAGE and opts.__runner__ is native: + if (opts.image != docker.DEFAULT_IMAGE # type: ignore + and opts.__runner__ is native): warn(dedent(""" Warning: The specified --image=%s option is not used by --native. """ % opts.image)) diff --git a/nextstrain/cli/types.py b/nextstrain/cli/types.py index 4e1d85b1..6961d66b 100644 --- a/nextstrain/cli/types.py +++ b/nextstrain/cli/types.py @@ -1,10 +1,15 @@ """ -Type aliases for internal use. +Type definitions for internal use. """ import argparse import builtins -from typing import Any, List, Tuple, Union +import urllib.parse +from pathlib import Path +from types import ModuleType +from typing import Any, Iterable, List, Mapping, Optional, Tuple, Union +from typing_extensions import Protocol +from .volume import NamedVolume Options = argparse.Namespace @@ -16,3 +21,40 @@ # boto3.resources.factory, which means we can't use them here easily. :( S3Bucket = Any S3Object = Any + + +class RunnerModule(Protocol): + @staticmethod + def register_arguments(parser: argparse.ArgumentParser) -> None: ... + + @staticmethod + def run(opts: Options, + argv: List[str], + working_volume: Optional[NamedVolume], + extra_env: Mapping, + cpus: Optional[int], + memory: Optional[int]) -> int: + ... + + @staticmethod + def test_setup() -> Any: ... + + @staticmethod + def update() -> bool: ... + + @staticmethod + def versions() -> Iterable[str]: ... + + +class RemoteModule(Protocol): + @staticmethod + def upload(url: urllib.parse.ParseResult, local_files: List[Path]) -> Iterable[Tuple[Path, Path]]: ... + + @staticmethod + def download(url: urllib.parse.ParseResult, local_path: Path, recursively: bool = False) -> Iterable[Tuple[Path, Path]]: ... + + @staticmethod + def ls(url: urllib.parse.ParseResult) -> Iterable[Path]: ... + + @staticmethod + def delete(url: urllib.parse.ParseResult, recursively: bool = False) -> Iterable[Path]: ... diff --git a/nextstrain/cli/util.py b/nextstrain/cli/util.py index 3604419d..fa2e4a18 100644 --- a/nextstrain/cli/util.py +++ b/nextstrain/cli/util.py @@ -5,13 +5,14 @@ import subprocess import sys from types import ModuleType -from typing import Mapping, List, Tuple +from typing import Any, Mapping, List, Tuple from pathlib import Path from pkg_resources import parse_version from shutil import which from sys import exit, stderr, version_info as python_version from textwrap import dedent, indent from .__version__ import __version__ +from .types import RunnerModule def warn(*args): @@ -162,14 +163,14 @@ def exec_or_return(argv: List[str], extra_env: Mapping = {}) -> int: exit(process.returncode) -def runner_name(runner: ModuleType) -> str: +def runner_name(runner: RunnerModule) -> str: """ Return a friendly name suitable for display for the given runner module. """ return module_basename(runner).replace("_", "-") -def runner_help(runner: ModuleType) -> str: +def runner_help(runner: RunnerModule) -> str: """ Return a brief description of a runner module, suitable for help strings. """ @@ -179,7 +180,7 @@ def runner_help(runner: ModuleType) -> str: return "(undocumented)" -def module_basename(module: ModuleType) -> str: +def module_basename(module: Any) -> str: """ Return the final portion of the given module's name, akin to a file's basename. """ diff --git a/setup.py b/setup.py index 651d294f..c098d3d9 100644 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ def find_namespaced_packages(namespace): "netifaces >=0.10.6", "pyjwt[crypto] >=2.0.0", "requests", + "typing_extensions >=3.6.4", # We use fsspec's S3 support, which has a runtime dep on s3fs. s3fs # itself requires aiobotocore, which in turn requires very specific From 6bf05333c84a6c78bb12baed4de37a7d320970d7 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 13 Aug 2021 10:58:32 -0700 Subject: [PATCH 10/12] dev: Refine the type of the "exec" list for runners Not sure why it wasn't more specific before. --- nextstrain/cli/runner/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nextstrain/cli/runner/__init__.py b/nextstrain/cli/runner/__init__.py index 9b9f9ab7..02775280 100644 --- a/nextstrain/cli/runner/__init__.py +++ b/nextstrain/cli/runner/__init__.py @@ -1,7 +1,8 @@ import argparse +import builtins from argparse import ArgumentParser from textwrap import dedent -from typing import cast, Mapping, List, TYPE_CHECKING +from typing import cast, Mapping, List, Union, TYPE_CHECKING from . import ( docker as __docker, native as __native, @@ -60,9 +61,11 @@ % (configured_runner, runner_name(default_runner))) +RunnerExec = List[Union[str, 'builtins.ellipsis']] + def register_runners(parser: ArgumentParser, - exec: List, + exec: RunnerExec, runners: List[RunnerModule] = all_runners, default: RunnerModule = default_runner) -> None: """ @@ -114,7 +117,7 @@ def register_flags(parser: ArgumentParser, runners: List[RunnerModule], default: default = argparse.SUPPRESS) -def register_arguments(parser: ArgumentParser, runners: List[RunnerModule], exec: List) -> None: +def register_arguments(parser: ArgumentParser, runners: List[RunnerModule], exec: RunnerExec) -> None: """ Register arguments shared by all runners as well as runner-specific arguments on the given ArgumentParser instance. From c49aa0b332a99ec90fb8e07409a456801ae243fe Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 20:48:22 -0700 Subject: [PATCH 11/12] dev: Enable more type checking coverage with Mypy Closes a big gotcha where the bodies of functions weren't type checked if the argument or return types weren't annotated. Resolves #44. pyright-status: PASSING (but it wasn't flagging these) mypy-status: FAILING --- mypy.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypy.ini b/mypy.ini index d6423627..50561e15 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,6 +2,14 @@ # We currently aim for compat with 3.6. python_version = 3.6 +# Check function bodies which don't have a typed signature. This prevents a +# single untyped function from poisoning other typed functions in a call chain. +check_untyped_defs = True + +# Require functions with an annotated return type to be explicit about +# potentially returning None (via Optional[…]). +strict_optional = False + # In the future maybe we can contribute typing stubs for these modules (either # complete stubs in the python/typeshed repo or partial stubs just in # this repo), but for now that's more work than we want to invest. These From 25fd5ed888821807899e1e8d46adbf84c6c52819 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Thu, 12 Aug 2021 20:49:17 -0700 Subject: [PATCH 12/12] dev: Address issues flagged by Mypy in newly-covered code pyright-status: PASSING (again, its pre-existing state) mypy-status: PASSING --- nextstrain/cli/errors.py | 4 ++-- nextstrain/cli/runner/docker.py | 6 +++--- nextstrain/cli/types.py | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/nextstrain/cli/errors.py b/nextstrain/cli/errors.py index 264ec89c..b66eba59 100644 --- a/nextstrain/cli/errors.py +++ b/nextstrain/cli/errors.py @@ -9,9 +9,9 @@ class NextstrainCliError(Exception): pass class UserError(NextstrainCliError): - def __init__(self, message, *args, **kwargs): + def __init__(self, message): # Remove leading newlines, trailing whitespace, and then indentation # to better support nicely-formatted """multi-line strings""". formatted_message = dedent(message.lstrip("\n").rstrip()) - super().__init__("Error: " + formatted_message, *args, **kwargs) + super().__init__("Error: " + formatted_message) diff --git a/nextstrain/cli/runner/docker.py b/nextstrain/cli/runner/docker.py index 98591420..69be47dc 100644 --- a/nextstrain/cli/runner/docker.py +++ b/nextstrain/cli/runner/docker.py @@ -11,7 +11,7 @@ from textwrap import dedent from typing import Iterable, List from .. import runner, hostenv, config -from ..types import RunnerTestResults +from ..types import RunnerTestResults, RunnerTestResultStatus from ..util import warn, colored, capture_output, exec_or_return, resolve_path, split_image_name from ..volume import store_volume from ..__version__ import __version__ @@ -135,7 +135,7 @@ def test_memory_limit(): desired = 2 * GiB msg = 'containers have access to >%.0f GiB of memory' % (desired / GiB) - status = ... + status: RunnerTestResultStatus = ... if image_exists(): report_memory = """ @@ -169,7 +169,7 @@ def test_image_version(): minimum_tag = "build-20190119T045444Z" msg = 'image is new enough for this CLI version' - status = ... + status: RunnerTestResultStatus = ... repository, tag = split_image_name(DEFAULT_IMAGE) diff --git a/nextstrain/cli/types.py b/nextstrain/cli/types.py index 6961d66b..1df9b0a4 100644 --- a/nextstrain/cli/types.py +++ b/nextstrain/cli/types.py @@ -13,8 +13,9 @@ Options = argparse.Namespace -RunnerTestResult = Tuple[str, Union[bool, None, 'builtins.ellipsis']] -RunnerTestResults = List[RunnerTestResult] +RunnerTestResults = List['RunnerTestResult'] +RunnerTestResult = Tuple[str, 'RunnerTestResultStatus'] +RunnerTestResultStatus = Union[bool, None, 'builtins.ellipsis'] # Cleaner-reading type annotations for boto3 S3 objects, which maybe can be # improved later. The actual types are generated at runtime in