From f81d557afc789b1cda6ed9d1e52dd192f00be399 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:49:32 +0100 Subject: [PATCH 01/48] :arrow_up: update dependencies --- poetry.lock | 503 ++++++++++++++++++++++++++-------------------------- 1 file changed, 256 insertions(+), 247 deletions(-) diff --git a/poetry.lock b/poetry.lock index 857c5884..8251ce90 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,21 +24,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "babel" -version = "2.9.0" +version = "2.9.1" description = "Internationalization utilities" category = "main" optional = true @@ -72,27 +72,34 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "7.1.2" +version = "8.0.3" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -104,7 +111,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.3.1" +version = "5.5" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -158,11 +165,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" @@ -174,7 +181,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "3.3.0" +version = "4.8.1" description = "Read metadata from Python packages" category = "dev" optional = false @@ -185,8 +192,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -198,17 +206,17 @@ python-versions = "*" [[package]] name = "jinja2" -version = "2.11.2" +version = "3.0.2" description = "A very fast and expressive template engine." category = "main" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -MarkupSafe = ">=0.23" +MarkupSafe = ">=2.0" [package.extras] -i18n = ["Babel (>=0.8)"] +i18n = ["Babel (>=2.7)"] [[package]] name = "livereload" @@ -224,11 +232,11 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "markupsafe" -version = "1.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = true -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" [[package]] name = "mypy" @@ -258,36 +266,37 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.8" +version = "21.2" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3" [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" @@ -310,15 +319,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pygments" -version = "2.7.3" +version = "2.10.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = true @@ -386,7 +395,7 @@ tests = ["pytest-isort"] [[package]] name = "pytz" -version = "2020.4" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "main" optional = true @@ -394,7 +403,7 @@ python-versions = "*" [[package]] name = "regex" -version = "2020.11.13" +version = "2021.10.23" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -402,25 +411,25 @@ python-versions = "*" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = true @@ -428,15 +437,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" -version = "2.0.0" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +version = "2.1.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "main" optional = true python-versions = "*" [[package]] name = "sphinx" -version = "3.4.0" +version = "3.5.4" description = "Python documentation generator" category = "main" optional = true @@ -446,7 +455,7 @@ python-versions = ">=3.5" alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.12,<0.17" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -462,7 +471,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.790)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -552,11 +561,11 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "1.0.3" +version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.6" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] @@ -587,7 +596,7 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.4" +version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." category = "main" optional = true @@ -623,7 +632,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false @@ -631,7 +640,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.26.2" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -652,15 +661,15 @@ python-versions = "*" [[package]] name = "zipp" -version = "3.4.0" +version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-autobuild", "sphinx-copybutton", "sphinx-autodoc-typehints"] @@ -684,82 +693,85 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] babel = [ - {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, - {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.3.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b"}, - {file = "coverage-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297"}, - {file = "coverage-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7"}, - {file = "coverage-5.3.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b"}, - {file = "coverage-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7"}, - {file = "coverage-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72"}, - {file = "coverage-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277"}, - {file = "coverage-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f"}, - {file = "coverage-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c"}, - {file = "coverage-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e"}, - {file = "coverage-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2"}, - {file = "coverage-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879"}, - {file = "coverage-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830"}, - {file = "coverage-5.3.1-cp38-cp38-win32.whl", hash = "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"}, - {file = "coverage-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606"}, - {file = "coverage-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d"}, - {file = "coverage-5.3.1-cp39-cp39-win32.whl", hash = "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98"}, - {file = "coverage-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1"}, - {file = "coverage-5.3.1-pp36-none-any.whl", hash = "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3"}, - {file = "coverage-5.3.1-pp37-none-any.whl", hash = "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c"}, - {file = "coverage-5.3.1.tar.gz", hash = "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b"}, + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] coveralls = [ {file = "coveralls-3.2.0-py2.py3-none-any.whl", hash = "sha256:aedfcc5296b788ebaf8ace8029376e5f102f67c53d1373f2e821515c15b36527"}, @@ -777,81 +789,83 @@ docutils = [ {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, - {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ - {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, - {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, + {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, + {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, ] livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mypy = [ {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, @@ -883,16 +897,16 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, - {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, + {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, + {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, @@ -903,12 +917,12 @@ py = [ {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pygments = [ - {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, - {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, + {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, + {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -926,67 +940,62 @@ pytest-pycodestyle = [ {file = "pytest-pycodestyle-2.2.0.tar.gz", hash = "sha256:e755e4235ed399760257026b8c3c696520848cb0261277c2df1c8e64af979eb2"}, ] pytz = [ - {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, - {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] regex = [ - {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, - {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, - {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, - {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, - {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, - {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, - {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, - {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, - {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, - {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, - {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, - {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, - {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, + {file = "regex-2021.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45b65d6a275a478ac2cbd7fdbf7cc93c1982d613de4574b56fd6972ceadb8395"}, + {file = "regex-2021.10.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74d071dbe4b53c602edd87a7476ab23015a991374ddb228d941929ad7c8c922e"}, + {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34d870f9f27f2161709054d73646fc9aca49480617a65533fc2b4611c518e455"}, + {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fb698037c35109d3c2e30f2beb499e5ebae6e4bb8ff2e60c50b9a805a716f79"}, + {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb46b542133999580ffb691baf67410306833ee1e4f58ed06b6a7aaf4e046952"}, + {file = "regex-2021.10.23-cp310-cp310-win32.whl", hash = "sha256:5e9c9e0ce92f27cef79e28e877c6b6988c48b16942258f3bc55d39b5f911df4f"}, + {file = "regex-2021.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ab7c5684ff3538b67df3f93d66bd3369b749087871ae3786e70ef39e601345b0"}, + {file = "regex-2021.10.23-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de557502c3bec8e634246588a94e82f1ee1b9dfcfdc453267c4fb652ff531570"}, + {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee684f139c91e69fe09b8e83d18b4d63bf87d9440c1eb2eeb52ee851883b1b29"}, + {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5095a411c8479e715784a0c9236568ae72509450ee2226b649083730f3fadfc6"}, + {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b568809dca44cb75c8ebb260844ea98252c8c88396f9d203f5094e50a70355f"}, + {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eb672217f7bd640411cfc69756ce721d00ae600814708d35c930930f18e8029f"}, + {file = "regex-2021.10.23-cp36-cp36m-win32.whl", hash = "sha256:a7a986c45d1099a5de766a15de7bee3840b1e0e1a344430926af08e5297cf666"}, + {file = "regex-2021.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:6d7722136c6ed75caf84e1788df36397efdc5dbadab95e59c2bba82d4d808a4c"}, + {file = "regex-2021.10.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f665677e46c5a4d288ece12fdedf4f4204a422bb28ff05f0e6b08b7447796d1"}, + {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450dc27483548214314640c89a0f275dbc557968ed088da40bde7ef8fb52829e"}, + {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:129472cd06062fb13e7b4670a102951a3e655e9b91634432cfbdb7810af9d710"}, + {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a940ca7e7189d23da2bfbb38973832813eab6bd83f3bf89a977668c2f813deae"}, + {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:530fc2bbb3dc1ebb17f70f7b234f90a1dd43b1b489ea38cea7be95fb21cdb5c7"}, + {file = "regex-2021.10.23-cp37-cp37m-win32.whl", hash = "sha256:ded0c4a3eee56b57fcb2315e40812b173cafe79d2f992d50015f4387445737fa"}, + {file = "regex-2021.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:391703a2abf8013d95bae39145d26b4e21531ab82e22f26cd3a181ee2644c234"}, + {file = "regex-2021.10.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be04739a27be55631069b348dda0c81d8ea9822b5da10b8019b789e42d1fe452"}, + {file = "regex-2021.10.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13ec99df95003f56edcd307db44f06fbeb708c4ccdcf940478067dd62353181e"}, + {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d1cdcda6bd16268316d5db1038965acf948f2a6f43acc2e0b1641ceab443623"}, + {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c186691a7995ef1db61205e00545bf161fb7b59cdb8c1201c89b333141c438a"}, + {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b20f544cbbeffe171911f6ce90388ad36fe3fad26b7c7a35d4762817e9ea69c"}, + {file = "regex-2021.10.23-cp38-cp38-win32.whl", hash = "sha256:c0938ddd60cc04e8f1faf7a14a166ac939aac703745bfcd8e8f20322a7373019"}, + {file = "regex-2021.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:56f0c81c44638dfd0e2367df1a331b4ddf2e771366c4b9c5d9a473de75e3e1c7"}, + {file = "regex-2021.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80bb5d2e92b2258188e7dcae5b188c7bf868eafdf800ea6edd0fbfc029984a88"}, + {file = "regex-2021.10.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1dae12321b31059a1a72aaa0e6ba30156fe7e633355e445451e4021b8e122b6"}, + {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f2b59c28afc53973d22e7bc18428721ee8ca6079becf1b36571c42627321c65"}, + {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d134757a37d8640f3c0abb41f5e68b7cf66c644f54ef1cb0573b7ea1c63e1509"}, + {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0dcc0e71118be8c69252c207630faf13ca5e1b8583d57012aae191e7d6d28b84"}, + {file = "regex-2021.10.23-cp39-cp39-win32.whl", hash = "sha256:a30513828180264294953cecd942202dfda64e85195ae36c265daf4052af0464"}, + {file = "regex-2021.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:0f7552429dd39f70057ac5d0e897e5bfe211629652399a21671e53f2a9693a4e"}, + {file = "regex-2021.10.23.tar.gz", hash = "sha256:f3f9a91d3cc5e5b0ddf1043c0ae5fa4852f18a1c0050318baf5fc7930ecc1f9c"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, + {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, + {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, ] sphinx = [ - {file = "Sphinx-3.4.0-py3-none-any.whl", hash = "sha256:77c801947eb86457822e01eadd5c2e2de020db0201f1f9fc98b0927980b6d212"}, - {file = "Sphinx-3.4.0.tar.gz", hash = "sha256:4dcde313801f23ea4789ac31e5405e240cb758b5d375804807f2f3cc3c396bfa"}, + {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"}, + {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"}, ] sphinx-autobuild = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, @@ -1013,8 +1022,8 @@ sphinxcontrib-devhelp = [ {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, - {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] sphinxcontrib-jsmath = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, @@ -1025,8 +1034,8 @@ sphinxcontrib-qthelp = [ {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, - {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, @@ -1108,19 +1117,19 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ - {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, - {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] From e5b02d22ef40478f92f41c560649b940a5e61737 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:52:12 +0100 Subject: [PATCH 02/48] :tada: first commit for date prompt feature :sparkles: add a simple date completer --- questionary/prompts/date.py | 169 ++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 questionary/prompts/date.py diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py new file mode 100644 index 00000000..78c9db6c --- /dev/null +++ b/questionary/prompts/date.py @@ -0,0 +1,169 @@ +"""Module for prompting for dates. + + +This module provides a prompting method for dates and times. Two basic types of +completion and validation are provided: :class: `SimpleDateCompleter` and :class: +`ParsingDateCompleter`. The former one, using only the build in module :module: +`datetime`, whereas the latter one offers the option to use third part libraries, e.g. +as `dateutil`_ or `dateparser`_, for completion. Similar class are there for validation +(:class: `SimpleDateValidator` and :class: `ParsingDateValidator`) Note that the +'simple' completer and validator currently only supports dates and not times, but the +'parsing' ones may support times as well (depending on the used third part library). +Both ``Completer`` and ``Validator``, the 'simple' and the 'parsing' ones are combined +in :class: `FullDateCompleter` and :class: `FullDateValidator`. + +Typical usage could look like this: + +Example: + >>> import questionary + >>> import dateutil + >>> questionary.date("Type a date: ").ask() + ? Type a date: 2021-01-01 + '2021-01-01' + >>> questionary.date("Type a date or time: ", parser=dateutil.parser.parse).ask() + ? Type a date or time: 2021-01-01 00:00:00 + '2021-01-01 00:00:00' + +.. _dateparser: https://github.com/scrapinghub/dateparser +.. _dateutil: https://github.com/dateutil/dateutil +""" + +from typing import Any +from typing import Callable +from typing import Iterable +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +import datetime + +from prompt_toolkit.completion import CompleteEvent +from prompt_toolkit.completion import Completer +from prompt_toolkit.completion import Completion +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text.base import to_formatted_text +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.lexers import SimpleLexer +from prompt_toolkit.shortcuts.prompt import CompleteStyle +from prompt_toolkit.shortcuts.prompt import PromptSession +from prompt_toolkit.styles import Style +from prompt_toolkit.styles import merge_styles +from prompt_toolkit.validation import ValidationError +from prompt_toolkit.validation import Validator + +from questionary.constants import DEFAULT_QUESTION_PREFIX +from questionary.constants import DEFAULT_STYLE +from questionary.prompts.common import build_validator +from questionary.question import Question + +# date format according to ISO8601 +ISO8601 = "%Y-%m-%d" + +# list of supported date formats +SUPPORTED_FORMATS = [ + ISO8601, + "%d.%m.%Y", + "%d-%m-%Y", + # ISO8601, + # "%d.%m.%y", + # "%d-%m-%y", + "%m-%d-%Y", + "%m/%d/%Y", + "%m.%d.%Y", +] + +# lists of completions (currently only numbers) +DAY = [str(i) for i in range(1, 32)] +MONTH = [str(i) for i in range(1, 13)] +YEAR = ["0" * (4 - len(str(i))) + str(i) for i in range(9999)] +HOUR = [str(i) for i in range(0, 24)] +MINUTE = [str(i) for i in range(60)] + +# dict used to determine correct order for completions +PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE} + + +AnyDate = Union[datetime.date, datetime.datetime, None] + + +################################### +# SIMPLE COMLETION AND VALIDATION +################################### + + +class SimpleDateCompleter(Completer): + def __init__(self, date_format: Optional[str] = None) -> None: + """__init__ of :class: `DateCompleter`. + + Offers completions for a date according to the chosen format. + + Args: + date_format (str): Format determining the format that is to be used + for parsing :class: `datetime.date`. + + Raises: + ValueError: if ``date_format`` is not in ``SUPPORTED_FORMATS``. + """ + self.format: str = date_format or ISO8601 + if self.format not in SUPPORTED_FORMATS: + raise ( + ValueError( + f"Date format '{self.format}' is not supported.\nSupported" + f" formats:\n{SUPPORTED_FORMATS}" + ) + ) + self.delimeter: str = self.format[-3] + + def _get_parse_order(self) -> List[str]: + """Returns the order for completions. + + Parses ``self.date_format`` into a list representing the order for completions, + e.g. [YEAR, MONTH, DAY]. + """ + parse_order = self.format.split(self.delimeter) + return [PARSE_FORMAT_DICT.get(item) for item in parse_order] + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """Completion of text input following a given ``date_format``. + + According what has allready been typed and the given ``date_format`` + completions for year, month and day are given (numbers only). + Date format ``date_format`` needs to be one of the ``SUPPORTED_FORMATS´´. + Supports only dates at the moment. + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + from the user input. + complete_event (CompleteEvent): The complete event. + + Yields: + Iterator[Iterable[Completion]]: The completions + """ + text_split = document.text.split(self.delimeter) + # the date information the user is currently typing, i.e. year, month or day + user_input = text_split[-1] + # old_input is the text the user has already typed + old_input = ( + self.delimeter.join(text_split[:-1]) + self.delimeter + if len(text_split) > 1 + else "" + ) + # get correct list of completion options + completion_list = self._get_parse_order()[len(text_split) - 1] + # find fitting completions in completion_list + for entry in completion_list: + if entry.startswith(user_input): + completion = entry if int(entry) >= 10 else "0" + entry + completion += self.delimeter if len(text_split) < 3 else "" + yield Completion( + old_input + completion, + start_position=-len(document.text), + style="fg:ansigreen bold", + selected_style="fg:white bg:ansired bold", + display=completion, + ) From 8a6d9e85c0ac78f6e8116355bad724d35b31b9e6 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:52:59 +0100 Subject: [PATCH 03/48] :sparkles: add simple date validation --- questionary/prompts/date.py | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 78c9db6c..83ed5656 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -167,3 +167,45 @@ def get_completions( selected_style="fg:white bg:ansired bold", display=completion, ) + + +class SimpleDateValidator(Validator): + def __init__(self, date_format: Optional[str] = None) -> None: + """__init__ of :class: `DateValidator`. + + Validates given input for dates. + + Args: + date_format (str): Format determining the format that is to be used + for parsing :class: `datetime.date`. + """ + self.format: str = date_format or ISO8601 + if self.format not in SUPPORTED_FORMATS: + raise ( + ValueError( + f"Date format '{self.format}' is not supported.\nSupported" + f" formats:\n{SUPPORTED_FORMATS}" + ) + ) + + def validate(self, document: Document) -> None: + """Validates the user input. + + Only inputs that can be parsed into `dateobject`_ via the `datetime-parser`_ are + accepted. + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + from the user input. + + Raises: + ValidationError: if parsing text input via ``datetime.datetime.strptime`` + fails. + + .. _dateobject: :class: `datetime.datetime` + .. _datetime_parser: :function: `datetime.datetime.strptime` + """ + try: + datetime.datetime.strptime(document.text, self.format) + except Exception: + raise (ValidationError(message="Invalid date.")) From 705fe033a8ec9e270bead36521db3a5380463719 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:54:43 +0100 Subject: [PATCH 04/48] :sparkles: add date completion via arbitrary date parsers --- questionary/prompts/date.py | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 83ed5656..02134378 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -209,3 +209,50 @@ def validate(self, document: Document) -> None: datetime.datetime.strptime(document.text, self.format) except Exception: raise (ValidationError(message="Invalid date.")) + + +################################### +# PARSING COMLETION AND VALIDATION +################################### + + +class ParsingDateCompleter(Completer): + def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: + """__init__ of :class: `ParsingDateCompleter`. + + Completes the date using a parser. + + Args: + parser (Optional[Callable[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + self.parser = parser or (lambda: None) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """Completions using a date parser. + + Calls a parser, which converts the string typed by the user into some date + object (:class: `datetime.date` or :class: `datetime.date`) and yields its + string as the completion. + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + from the user input. + complete_event (CompleteEvent): The complete event. + + Yields: + Iterator[Iterable[Completion]]: The completions + """ + try: + parsed_date = self.parser(document.text) + except Exception: + parsed_date = None + # if input can be parsed, yield the string of the parsed date as completion + if parsed_date is not None: + yield Completion(str(parsed_date), start_position=-len(document.text)) From af880165f85435bcd89debc5bcce1056537d6e8b Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:55:25 +0100 Subject: [PATCH 05/48] :sparkles: add date validation via arbitrary date parsers --- questionary/prompts/date.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 02134378..2d3304d9 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -256,3 +256,44 @@ def get_completions( # if input can be parsed, yield the string of the parsed date as completion if parsed_date is not None: yield Completion(str(parsed_date), start_position=-len(document.text)) + + +class ParsingDateValidator(Validator): + """Validator class for date validation via a parser.""" + + def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: + """__init__ of :class: `ParsingDateValidator`. + + Validates the string input for :function: `date` using a parser. + + Args: + parser (Optional[Callable[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + self.parser = parser + + def validate(self, document: Document) -> None: + """Validates the date input using a date parser. + + If `self.parser` is None, no validation is performed. Otherwise, only those + inputs that can be parsed to a valid dateobject (:class: `datetime.date` or + :class: `datetime.date`) are considered to be valid. + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + of the user input. + + Raises: + ValidationError: if parsing text input via ``self.parser`` fails. + """ + try: + parsed_date = self.parser(document.text) + except Exception: + parsed_date = None + if self.parser is not None: + if parsed_date is None: + raise (ValidationError(message="Can not parse input to date object.")) From 6f5e2aa6229755b84472aa0047f03fc65080e3c0 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:56:33 +0100 Subject: [PATCH 06/48] :sparkles: add date completion combining simple completion and parser method --- questionary/prompts/date.py | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 2d3304d9..3116520e 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -297,3 +297,73 @@ def validate(self, document: Document) -> None: if self.parser is not None: if parsed_date is None: raise (ValidationError(message="Can not parse input to date object.")) + + +################################### +# FULL COMLETION AND VALIDATION +################################### + + +class FullDateCompleter(Completer): + """Completer for date prompts. + + Yields completions from both :class: `SimpleDateCompleter´ and :class: + `ParsingDateCompleter` + """ + + def __init__( + self, + date_format: Optional[str] = ISO8601, + parser: Optional[Callable[[str], AnyDate]] = None, + ) -> None: + """Completer for date prompts. + + Yields completions from both :class: `SimpleDateCompleter´ and :class: + `ParsingDateCompleter` + + Args: + date_format (str): Format determining the format that is to be used + for parsing :class: `datetime.date`. If set to None, completion of + :class: `SimpleDateCompleter´ is deactivated. Defaults to ´´ISO8601``. + parser (Optional[Callable[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + self.date_format = date_format + self.parser = parser + self.simple_completer = SimpleDateCompleter(date_format=date_format) + self.parsing_completer = ParsingDateCompleter(parser=parser) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """Completion method of :class: `FullDateCompleter`. + + Yields completions from both :class: `SimpleDateCompleter´ and :class: + `ParsingDateCompleter` + + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + from the user input. + complete_event (CompleteEvent): The complete event. + + Yields: + Iterator[Iterable[Completion]]: The completions + """ + if self.date_format is not None: + simple_completions = ( + self.simple_completer.get_completions(document, complete_event) or [] + ) + else: + simple_completions = [] + parsed_completions = ( + self.parsing_completer.get_completions(document, complete_event) or [] + ) + for completion in simple_completions: + yield completion + for completion in parsed_completions: + yield completion From df601639c8b17d35d382621155aafb16833b3967 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:57:15 +0100 Subject: [PATCH 07/48] :sparkles: add date validation combining simple validation and parser method --- questionary/prompts/date.py | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 3116520e..baa1a7ff 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -367,3 +367,64 @@ def get_completions( yield completion for completion in parsed_completions: yield completion + + +class FullDateValidator(Validator): + """Validation of user input for :function: `date`. + + Validates the user input for :func: `date` via calling validation of :class: + `ParsingDateValidator` (if ``parsing`` is a callable) or via calling ``validate`` of + :class: `SimpleDateValidator`. + + Args: + date_format (str): Format determining the format that is to be used + for parsing :class: `datetime.date`. + parser (Callable[[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + + def __init__( + self, + date_format: Optional[str] = ISO8601, + parser: Optional[Callable[[str], AnyDate]] = None, + ) -> None: + """__init__ of :class: `FullDateValidator`. + + Validates the user input for :func: `date` via calling validation of :class: + `ParsingDateValidator` (if ``parser`` is a callable) or via calling + ``validate`` of :class: `SimpleDateValidator`. + + Args: + date_format (str): Format determining the format that is to be used + for parsing :class: `datetime.date`. Defaults to ``ISO8601``. + parser (Callable[[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + self.date_format = date_format + self.parser = parser + self.simple_validator = SimpleDateValidator(date_format=date_format) + self.parsing_validator = ParsingDateValidator(parser=parser) + + def validate(self, document: Document) -> None: + """Validaton of user input for :function: `date`. + + Validates the user input for :func: `date` via calling validation of :class: + `ParsingDateValidator` (if ``parsing`` is a callable) or via calling + ``validate`` of :class: `SimpleDateValidator`. + + Args: + document (Document): The :class: `prompt_toolkit.document.Document` created + of the user input. + """ + if self.parser is None: + self.simple_validator.validate(document) + else: + self.parsing_validator.validate(document) From 3130aad20abacc540582cfee5c9cf92e48256762 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:57:49 +0100 Subject: [PATCH 08/48] :sparkles: add the date prompt method --- questionary/prompts/date.py | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index baa1a7ff..d316c245 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -428,3 +428,134 @@ def validate(self, document: Document) -> None: self.simple_validator.validate(document) else: self.parsing_validator.validate(document) + + +################################### +# THE PROMPT +################################### + + +def date( + message: str, + default: str = "", + qmark: str = DEFAULT_QUESTION_PREFIX, + validate: Any = None, + completer: Completer = None, + style: Optional[Style] = None, + date_format: Optional[str] = ISO8601, + print_date_format: bool = True, + parser: Optional[Callable[[str], AnyDate]] = None, + complete_style: CompleteStyle = CompleteStyle.MULTI_COLUMN, + **kwargs: Any, +) -> Question: + """A text input for a date or time with autocompletion enabled. + + Args: + message (str): Question text. + default (str): Default return value (single value). Defaults to "". + qmark (str): Question prefix displayed in front of the question. By default this + is a ``?``. Defaults to DEFAULT_QUESTION_PREFIX. + validate (Any, optional): Require the entered value to pass a validation. The + value can not be submitted until the validator accepts it (e.g. to check + minimum password length). This can either be a function accepting the input and + returning a boolean, or an class reference to a subclass of the prompt toolkit + Validator class. Defaults to None. + completer (Completer, optional): A :class: `prompt_toolkit.completion.Completer` + yielding completions for user input. If None, :class: `FullDateCompleter` is + used. Defaults to None. Defaults to None. + style (Style, optional): A custom color and style for the question parts. You + can configure colors as well as font types for different elements. + Defaults to None. + date_format (str, optional): Format determining the format that is to be used + for parsing :class: `datetime.date`. If set to None (and no ``completer`` was + set) completions of :class: `SimpleDateCompleter` are deactivated.Defaults to + ``ISO8601``.. Defaults to ISO8601. + print_date_format (bool): If set to True, ``date_format`` is printed on the + right of the prompt. Defaults to True. + parser (Callable[[str], AnyDate], optional): A callable that parses + a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. + the ones from `dateparser`_ or `dateutil`_. Defaults to None. + complete_style (CompleteStyle): How autocomplete menu would be shown, it could + be ``COLUMN`` ``MULTI_COLUMN`` or ``READLINE_LIKE`` from + :class:`prompt_toolkit.shortcuts.CompleteStyle`. Defaults to + CompleteStyle.MULTI_COLUMN. + + Returns: + :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). + + Example: + >>> import questionary + >>> questionary.date("Type a date: ").ask() + ? Type a date: 2021-01-01 + '2021-01-01' + + .. _dateparser: https://github.com/scrapinghub/dateparser + .. _dateutil: https://github.com/dateutil/dateutil + """ + # delimeter used to separate year, month and day + delimeter: str = date_format[-3] + + merged_style = merge_styles([DEFAULT_STYLE, style]) + + if print_date_format: + rprompt = to_formatted_text( + f"Date format: {date_format}", style="bg: ansigreen bold" + ) + else: + rprompt = None + + def get_prompt_tokens() -> List[Tuple[str, str]]: + return [("class:qmark", qmark), ("class:question", " {} ".format(message))] + + # set validator + validate = validate or FullDateValidator(date_format=date_format, parser=parser) + validator: Validator = build_validator(validate) + + # set completer + completer = completer or FullDateCompleter(date_format=date_format, parser=parser) + + bindings = KeyBindings() + + # set behavior on `carriage return` + @bindings.add(Keys.ControlM, eager=True) + def set_answer(event: KeyPressEvent): + if event.current_buffer.complete_state is not None: + event.current_buffer.complete_state = None + elif event.app.current_buffer.validate(set_cursor=True): + # When the validation succeeded, accept the input. + result_date = event.app.current_buffer.document.text + if result_date.endswith(delimeter): + result_date = result_date[:-1] + + event.app.exit(result=result_date) + event.app.current_buffer.append_to_history() + + # delimeter should not be placed twice + @bindings.add(delimeter, eager=True) + def next_segment(event: KeyPressEvent): + b = event.app.current_buffer + + if b.complete_state: + b.complete_state = None + + current_date = b.document.text + if not current_date.endswith(delimeter): + b.insert_text(delimeter) + + b.start_completion(select_first=False) + + # initiate ``PromptSession`` + p = PromptSession( + get_prompt_tokens, + lexer=SimpleLexer("class:answer"), + style=merged_style, + completer=completer, + validator=validator, + complete_style=complete_style, + key_bindings=bindings, + rprompt=rprompt, + **kwargs, + ) + p.default_buffer.reset(Document(default)) + + return Question(p.app) From 843d14908b8cd2401fe3e42d69088f18b74341df Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 16:59:06 +0100 Subject: [PATCH 09/48] :memo: add documentation for new date method --- docs/pages/types.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/pages/types.rst b/docs/pages/types.rst index bef5d0e7..7260f288 100644 --- a/docs/pages/types.rst +++ b/docs/pages/types.rst @@ -15,6 +15,8 @@ with the available question types**: * use :ref:`type_path` to ask for a **file or directory** path with autocompletion +* use :ref:`type_date` to ask for a **date** or **time** with validation and autocompletion + * use :ref:`type_confirm` to ask a **yes or no** question * use :ref:`type_select` to ask the user to select **one item** from a beautiful list @@ -46,6 +48,13 @@ File Path .. automethod:: questionary::path +.. _type_date: + +Dates and Times +############### + +.. automethod:: questionary::date + .. _type_confirm: Confirmation From cd25e2cf3b9759672d055c44b880ad1d0d33027b Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 17:23:50 +0100 Subject: [PATCH 10/48] :white_check_mark: add 'date' to AVAILABLE_PROMPTS --- questionary/prompts/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/questionary/prompts/__init__.py b/questionary/prompts/__init__.py index 053ddeb2..f3e8dc39 100644 --- a/questionary/prompts/__init__.py +++ b/questionary/prompts/__init__.py @@ -6,6 +6,7 @@ from questionary.prompts import password from questionary.prompts import checkbox from questionary.prompts import path +from questionary.prompts import date AVAILABLE_PROMPTS = { "autocomplete": autocomplete.autocomplete, @@ -16,6 +17,7 @@ "password": password.password, "checkbox": checkbox.checkbox, "path": path.path, + "date": date.date, # backwards compatible names "list": select.select, "rawlist": rawselect.rawselect, From 5b3058d0278671b4af8cd7489984c1cf3df4036e Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 18:13:16 +0100 Subject: [PATCH 11/48] :memo: add date to README.md --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 90d04d3c..f7a60160 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,15 @@ ✨ Questionary is a Python library for effortlessly building pretty command line interfaces ✨ -* [Features](#features) -* [Installation](#installation) -* [Usage](#usage) -* [Documentation](#documentation) -* [Support](#support) +- [Questionary](#questionary) + - [Features](#features) + - [Installation](#installation) + - [Usage](#usage) + - [Documentation](#documentation) + - [Support](#support) + - [Contributing](#contributing) + - [Authors and Acknowledgment](#authors-and-acknowledgment) + - [License](#license) ![Example](https://raw.githubusercontent.com/tmbo/questionary/master/docs/images/example.gif) @@ -24,6 +28,7 @@ import questionary questionary.text("What's your first name").ask() questionary.password("What's your secret?").ask() questionary.confirm("Are you amazed?").ask() +questionary.date("Please enter your date of birth: ).ask() questionary.select( "What do you want to do?", @@ -49,10 +54,11 @@ Used and supported by ## Features Questionary supports the following input prompts: - + * [Text](https://questionary.readthedocs.io/en/stable/pages/types.html#text) * [Password](https://questionary.readthedocs.io/en/stable/pages/types.html#password) * [File Path](https://questionary.readthedocs.io/en/stable/pages/types.html#file-path) + * [Dates and Times](https://questionary.readthedocs.io/en/stable/pages/types.html#type-date) * [Confirmation](https://questionary.readthedocs.io/en/stable/pages/types.html#confirmation) * [Select](https://questionary.readthedocs.io/en/stable/pages/types.html#select) * [Raw select](https://questionary.readthedocs.io/en/stable/pages/types.html#raw-select) @@ -107,7 +113,7 @@ Contributions are very much welcomed and appreciated. Head over to the documenta Questionary is written and maintained by Tom Bocklisch and Kian Cross. -It is based on the great work by [Oyetoke Toby](https://github.com/CITGuru/PyInquirer) +It is based on the great work by [Oyetoke Toby](https://github.com/CITGuru/PyInquirer) and [Mark Fink](https://github.com/finklabs/whaaaaat). ## License From 8df9a1b2718eca74c7e2eb24da86faa3505fa76a Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Sun, 31 Oct 2021 18:44:54 +0100 Subject: [PATCH 12/48] :sparkles: 'date' return now by default 'datetime.datetime' --- questionary/prompts/date.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index d316c245..54906974 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -12,6 +12,8 @@ Both ``Completer`` and ``Validator``, the 'simple' and the 'parsing' ones are combined in :class: `FullDateCompleter` and :class: `FullDateValidator`. +By default date returns an :class: `datetime.datetime` instance. + Typical usage could look like this: Example: @@ -445,6 +447,7 @@ def date( date_format: Optional[str] = ISO8601, print_date_format: bool = True, parser: Optional[Callable[[str], AnyDate]] = None, + return_date_object: bool = True, complete_style: CompleteStyle = CompleteStyle.MULTI_COLUMN, **kwargs: Any, ) -> Question: @@ -475,6 +478,8 @@ def date( parser (Callable[[str], AnyDate], optional): A callable that parses a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. the ones from `dateparser`_ or `dateutil`_. Defaults to None. + return_date_object (bool): If True, a parsed date object is returned. Else, string + the text input is returned. Defaults to True. complete_style (CompleteStyle): How autocomplete menu would be shown, it could be ``COLUMN`` ``MULTI_COLUMN`` or ``READLINE_LIKE`` from :class:`prompt_toolkit.shortcuts.CompleteStyle`. Defaults to @@ -507,6 +512,15 @@ def date( def get_prompt_tokens() -> List[Tuple[str, str]]: return [("class:qmark", qmark), ("class:question", " {} ".format(message))] + def _parse_to_date(input: str) -> datetime.datetime: + if return_date_object: + if parser is not None: + return parser(input) + else: + return datetime.datetime.strptime(input, format=date_format) + else: + return input + # set validator validate = validate or FullDateValidator(date_format=date_format, parser=parser) validator: Validator = build_validator(validate) @@ -527,7 +541,7 @@ def set_answer(event: KeyPressEvent): if result_date.endswith(delimeter): result_date = result_date[:-1] - event.app.exit(result=result_date) + event.app.exit(result=_parse_to_date(result_date)) event.app.current_buffer.append_to_history() # delimeter should not be placed twice From a186394a7c837bb4c4d7d9855449c8d6eea80243 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 12:04:48 +0100 Subject: [PATCH 13/48] :sparkles: add times to 'simple' completion and validation --- questionary/prompts/date.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 54906974..fa03d4b3 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -6,11 +6,12 @@ `ParsingDateCompleter`. The former one, using only the build in module :module: `datetime`, whereas the latter one offers the option to use third part libraries, e.g. as `dateutil`_ or `dateparser`_, for completion. Similar class are there for validation -(:class: `SimpleDateValidator` and :class: `ParsingDateValidator`) Note that the -'simple' completer and validator currently only supports dates and not times, but the -'parsing' ones may support times as well (depending on the used third part library). -Both ``Completer`` and ``Validator``, the 'simple' and the 'parsing' ones are combined -in :class: `FullDateCompleter` and :class: `FullDateValidator`. +(:class: `SimpleDateValidator` and :class: `ParsingDateValidator`). Note that the +'simple' completer and validator currently only supports dates or times, but not both at +the same time. The 'parsing' ones, on the other hand, may support times as well +(depending on the used third part library). Both ``Completer`` and ``Validator``, the +'simple' and the 'parsing' ones are combined in :class: `FullDateCompleter` +and :class: `FullDateValidator`. By default date returns an :class: `datetime.datetime` instance. @@ -63,10 +64,12 @@ # date format according to ISO8601 ISO8601 = "%Y-%m-%d" +ISO8601_TIME = "%H:%M:%S" # list of supported date formats SUPPORTED_FORMATS = [ ISO8601, + ISO8601_TIME, "%d.%m.%Y", "%d-%m-%Y", # ISO8601, @@ -83,6 +86,7 @@ YEAR = ["0" * (4 - len(str(i))) + str(i) for i in range(9999)] HOUR = [str(i) for i in range(0, 24)] MINUTE = [str(i) for i in range(60)] +SECOND = [str[i] for i in range(60)] # dict used to determine correct order for completions PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE} @@ -517,7 +521,7 @@ def _parse_to_date(input: str) -> datetime.datetime: if parser is not None: return parser(input) else: - return datetime.datetime.strptime(input, format=date_format) + return datetime.datetime.strptime(input, date_format) else: return input From 5dece609b19f2e4bc43735c49bafba12c0f8427b Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 12:34:38 +0100 Subject: [PATCH 14/48] :pencil2: used the wrong brackets --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index fa03d4b3..ca74e51d 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -86,7 +86,7 @@ YEAR = ["0" * (4 - len(str(i))) + str(i) for i in range(9999)] HOUR = [str(i) for i in range(0, 24)] MINUTE = [str(i) for i in range(60)] -SECOND = [str[i] for i in range(60)] +SECOND = [str(i) for i in range(60)] # dict used to determine correct order for completions PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE} From 4efdf90adbee0e4ee64a0ab070d76cb26857277e Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:01:23 +0100 Subject: [PATCH 15/48] :bug: forgot to add 'SECOND' to 'PARSE_FORMAT_DICT' --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index ca74e51d..c25e5459 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -89,7 +89,7 @@ SECOND = [str(i) for i in range(60)] # dict used to determine correct order for completions -PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE} +PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE, "%S": SECOND} AnyDate = Union[datetime.date, datetime.datetime, None] From 4dc42455f47dcda6640fd1337a02387f5c8592ec Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:03:25 +0100 Subject: [PATCH 16/48] :white_check_mark: added several tests for 'date' :white_check_mark: add various tests for 'SimpleDateCompleter' --- tests/prompts/test_date.py | 114 +++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/prompts/test_date.py diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py new file mode 100644 index 00000000..ecc8beb4 --- /dev/null +++ b/tests/prompts/test_date.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +"""Testsuite for date module.""" +from typing import List, Optional + +import datetime + +import prompt_toolkit +from prompt_toolkit.completion.base import CompleteEvent +import pytest +from prompt_toolkit import document + +from questionary.prompts import date +from tests.utils import KeyInputs, feed_cli_with_input + + +def _check_simple_completions(date_format: str, text_inputs: List[str], order: List[str], delimeter: str) -> None: + """Checks completions of `SimpleDateCompleter`. + + Completions should follow the order of ``order`` and use the delimeter + ``delimeter``. + + Args: + date_format (str): The ``date_format`` for the :class: `SimpleDateCompleter`. + text_inputs (str): Several text inputs that are given to the completer. + order (List[str]): Expected order of completions. Needs to be of the same length as ``text_inputs``. + delimeter (str): The expected seperating delimeter. + + Raises: + ValueError: if ``order`` and ``text_inputs`` are not of the same length. + """ + if len(text_inputs) != len(order): + raise(ValueError("'tect_inputs' and 'order' need to have the same number of items.")) + + # create ``Completer`` + completer = date.SimpleDateCompleter(date_format=date_format) + + def _returns_expected_completions(text: str, options: List[str], delimeter: str) -> None: + """Checks if completions for ``text`` are in ``options``.""" + input = document.Document(text) + # check if completions are expected ones + for completion in completer.get_completions(document=input, complete_event=CompleteEvent()): + assert completion.text in map(lambda x: x + delimeter, options) + + # last entry should use no delimeter + delimeters: List[str] = [delimeter for _ in range(len(text_inputs) - 1)] + [""] + for i in range(len(order)): + _returns_expected_completions( + text=text_inputs[i], + options=order[i], + delimeter=delimeters[i] + ) + + +def custom_date_parser(input: str) -> Optional[datetime.date]: + """Some date parser.""" + try: + _date = datetime.datetime.strptime(input, "%Y-%m-%d %H:%M") + except Exception: + _date = None + return _date + + +def test_simple_date_completer(): + """Yields completions depending on the chosen ``date_format``.""" + # check order of completions + _check_simple_completions( + date_format=date.ISO8601, + text_inputs=["202", "2021-02", "2021-02-02"], + order=[date.YEAR, date.MONTH, date.DAY], + delimeter="-" + ) + + # check order of completions for other format + _check_simple_completions( + date_format=date.ISO8601_TIME, + text_inputs=["20", "20:02", "2021:02:02"], + order=[date.HOUR, date.MINUTE, date.SECOND], + delimeter=":" + ) + # completer = date.SimpleDateCompleter(date_format=date.ISO8601_TIME) + # input = document.Document("20") + # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x + ":", date.HOUR) + # input = document.Document("20:02") + # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x + ":", date.MONTH) + # input = document.Document("2021:02:02") + # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x, date.SECOND) + + +def test_simple_date_completer_exception(): + """Raises ``ValueError``, if ``date_format`` is not supported.""" + # supported date_formats are allowed + for date_format in date.SUPPORTED_FORMATS: + date.SimpleDateCompleter(date_format=date_format) + + # not supported ones make it raise ``ValueError`` + with pytest.raises(ValueError): + date.SimpleDateCompleter(date_format="not-supported") + + +def test_date(): + """Test date on default behavior.""" + message = "Type a date: " + text = "2021-01-01" + KeyInputs.ENTER + result, cli = feed_cli_with_input("date", message, text) + assert result == datetime.datetime(2021, 1, 1 , 0, 0) + + +def test_date_string_return(): + """Date may return a string.""" + message = "Type a date: " + text = "2021-01-01" + KeyInputs.ENTER + result, cli = feed_cli_with_input("date", message, text, return_date_object=False) + assert result == "2021-01-01" + From bbaf2c181b71f5a973457790ea4682f90b62009a Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:04:07 +0100 Subject: [PATCH 17/48] :coffin: remove old code --- tests/prompts/test_date.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index ecc8beb4..22f42f9d 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -77,13 +77,6 @@ def test_simple_date_completer(): order=[date.HOUR, date.MINUTE, date.SECOND], delimeter=":" ) - # completer = date.SimpleDateCompleter(date_format=date.ISO8601_TIME) - # input = document.Document("20") - # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x + ":", date.HOUR) - # input = document.Document("20:02") - # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x + ":", date.MONTH) - # input = document.Document("2021:02:02") - # assert completer.get_completions(document=input, complete_event=CompleteEvent()) in map(lambda x: x, date.SECOND) def test_simple_date_completer_exception(): From 240b500f455d6853a00df99bf6ef6168a4c111a4 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:06:13 +0100 Subject: [PATCH 18/48] :white_check_mark: add exception test for 'SimpleDateValidator' --- tests/prompts/test_date.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 22f42f9d..cc6b8f0e 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -90,6 +90,17 @@ def test_simple_date_completer_exception(): date.SimpleDateCompleter(date_format="not-supported") +def test_simple_date_validator_exception(): + """Raises ``ValueError``, if ``date_format`` is not supported.""" + # supported date_formats are allowed + for date_format in date.SUPPORTED_FORMATS: + date.SimpleDateValidator(date_format=date_format) + + # not supported ones make it raise ``ValueError`` + with pytest.raises(ValueError): + date.SimpleDateValidator(date_format="not-supported") + + def test_date(): """Test date on default behavior.""" message = "Type a date: " From 0f9271f95587f6b23eeba53d0d1b65702cfe3ff7 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:09:02 +0100 Subject: [PATCH 19/48] :art: formatted code using black --- tests/prompts/test_date.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index cc6b8f0e..4e6233e6 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -5,15 +5,17 @@ import datetime import prompt_toolkit -from prompt_toolkit.completion.base import CompleteEvent import pytest from prompt_toolkit import document +from prompt_toolkit.completion.base import CompleteEvent from questionary.prompts import date from tests.utils import KeyInputs, feed_cli_with_input -def _check_simple_completions(date_format: str, text_inputs: List[str], order: List[str], delimeter: str) -> None: +def _check_simple_completions( + date_format: str, text_inputs: List[str], order: List[str], delimeter: str +) -> None: """Checks completions of `SimpleDateCompleter`. Completions should follow the order of ``order`` and use the delimeter @@ -22,32 +24,39 @@ def _check_simple_completions(date_format: str, text_inputs: List[str], order: L Args: date_format (str): The ``date_format`` for the :class: `SimpleDateCompleter`. text_inputs (str): Several text inputs that are given to the completer. - order (List[str]): Expected order of completions. Needs to be of the same length as ``text_inputs``. + order (List[str]): Expected order of completions. Needs to be of the same length + as ``text_inputs``. delimeter (str): The expected seperating delimeter. Raises: ValueError: if ``order`` and ``text_inputs`` are not of the same length. """ if len(text_inputs) != len(order): - raise(ValueError("'tect_inputs' and 'order' need to have the same number of items.")) + raise ( + ValueError( + "'tect_inputs' and 'order' need to have the same number of items." + ) + ) # create ``Completer`` completer = date.SimpleDateCompleter(date_format=date_format) - def _returns_expected_completions(text: str, options: List[str], delimeter: str) -> None: + def _returns_expected_completions( + text: str, options: List[str], delimeter: str + ) -> None: """Checks if completions for ``text`` are in ``options``.""" input = document.Document(text) # check if completions are expected ones - for completion in completer.get_completions(document=input, complete_event=CompleteEvent()): + for completion in completer.get_completions( + document=input, complete_event=CompleteEvent() + ): assert completion.text in map(lambda x: x + delimeter, options) # last entry should use no delimeter delimeters: List[str] = [delimeter for _ in range(len(text_inputs) - 1)] + [""] for i in range(len(order)): _returns_expected_completions( - text=text_inputs[i], - options=order[i], - delimeter=delimeters[i] + text=text_inputs[i], options=order[i], delimeter=delimeters[i] ) @@ -67,7 +76,7 @@ def test_simple_date_completer(): date_format=date.ISO8601, text_inputs=["202", "2021-02", "2021-02-02"], order=[date.YEAR, date.MONTH, date.DAY], - delimeter="-" + delimeter="-", ) # check order of completions for other format @@ -75,7 +84,7 @@ def test_simple_date_completer(): date_format=date.ISO8601_TIME, text_inputs=["20", "20:02", "2021:02:02"], order=[date.HOUR, date.MINUTE, date.SECOND], - delimeter=":" + delimeter=":", ) @@ -106,7 +115,7 @@ def test_date(): message = "Type a date: " text = "2021-01-01" + KeyInputs.ENTER result, cli = feed_cli_with_input("date", message, text) - assert result == datetime.datetime(2021, 1, 1 , 0, 0) + assert result == datetime.datetime(2021, 1, 1, 0, 0) def test_date_string_return(): @@ -115,4 +124,3 @@ def test_date_string_return(): text = "2021-01-01" + KeyInputs.ENTER result, cli = feed_cli_with_input("date", message, text, return_date_object=False) assert result == "2021-01-01" - From ed54148462bdc39afe1dcb946f1bc9ba6a8b5cc7 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 13:45:50 +0100 Subject: [PATCH 20/48] :children_crossing: add a custom date parser for ISO8601 --- questionary/prompts/date.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index c25e5459..d5ce0aa7 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -39,6 +39,7 @@ from typing import Tuple from typing import Union +import re import datetime from prompt_toolkit.completion import CompleteEvent @@ -95,6 +96,53 @@ AnyDate = Union[datetime.date, datetime.datetime, None] +def custom_date_parser(input: str) -> Optional[datetime.date]: + """A very simple date parser. + + Parses dates given by ISO8601. + + Args: + input (str): Text input that is to be parsed. + + Returns: + Optional[datetime.date]: The parsed :class: `datetime.datetime` instance. None, + if parsing failed. + """ + _time_format_codes = [ + "%Y", + "%m", + "%d", + "%H", + "%M", + "%S" + ] + date_formats = [ + "".join(_time_format_codes[0:i]) for i in range(len(_time_format_codes)) + ] + + def _try_date_format(date_format:str, text:str) -> Optional[datetime.datetime]: + """Tries to parse ``text`to :class: `datetime.datetime`.""" + try: + return datetime.datetime.strptime(text, date_format) + except Exception: + return None + + # remove all delimeters from the input + pattern = re.compile("[\d]") + relevant_input = "".join(pattern.findall(input)) + # try parsing for the several date_formats + for date_format in date_formats: + _date = _try_date_format( + date_format=date_format, + text=relevant_input + ) + # if parsing succeeded return the result + if _date is not None: + return _date + # no parsing succeeded so return None + return None + + ################################### # SIMPLE COMLETION AND VALIDATION ################################### From 5092e635e176e743b0c03c7b20c0792ce8a169c9 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:09:02 +0100 Subject: [PATCH 21/48] :boom: default parser is now 'custom_date_parser' :memo: updated documentation according to new default parser' :memo: added examples to 'custom_date_parser' --- questionary/prompts/date.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index d5ce0aa7..4475959a 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -4,14 +4,16 @@ This module provides a prompting method for dates and times. Two basic types of completion and validation are provided: :class: `SimpleDateCompleter` and :class: `ParsingDateCompleter`. The former one, using only the build in module :module: -`datetime`, whereas the latter one offers the option to use third part libraries, e.g. +`datetime`, whereas the latter one offers the option to easily include third part libraries, e.g. as `dateutil`_ or `dateparser`_, for completion. Similar class are there for validation (:class: `SimpleDateValidator` and :class: `ParsingDateValidator`). Note that the 'simple' completer and validator currently only supports dates or times, but not both at the same time. The 'parsing' ones, on the other hand, may support times as well (depending on the used third part library). Both ``Completer`` and ``Validator``, the 'simple' and the 'parsing' ones are combined in :class: `FullDateCompleter` -and :class: `FullDateValidator`. +and :class: `FullDateValidator`. If no parser is given to the 'full' completer and +validator, :function: `custom_date_parser` is used by default. If you want to deactivate +'parsing' validation and completion, ``parser`` needs to be (lambda: None). By default date returns an :class: `datetime.datetime` instance. @@ -99,14 +101,29 @@ def custom_date_parser(input: str) -> Optional[datetime.date]: """A very simple date parser. - Parses dates given by ISO8601. + Tries to parse text inputs into :class: ´datetime.datetime` objects. Assumes + the input to follow ISO8601_. Args: input (str): Text input that is to be parsed. Returns: Optional[datetime.date]: The parsed :class: `datetime.datetime` instance. None, - if parsing failed. + if parsing fails. + + Example: + >>> from questionary import date + >>> _date = date.custom_date_parser("2021-01-01 00:00:00") + >>> print(f"The formatted date is: {_date}.") + The formatted date is: 2021-01-01 00:00:00. + >>> assert _date == datetime.datetime(2021, 1, 1, 0, 0) + >>> _date_weird = date.custom_date_parser("2021.01 some 01 at time 12 :30") + >>> assert _date_weird == datetime.datetime(2021, 1, 1, 0, 0) + >>> print(f"The formatted date is: {_date_weird}.") + The formatted date is: 2021-01-01 00:00:00. + >>> assert date.custom_date_parser("this is no date') is None + + .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 """ _time_format_codes = [ "%Y", @@ -284,7 +301,7 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: .. _dateparser: https://github.com/scrapinghub/dateparser .. _dateutil: https://github.com/dateutil/dateutil """ - self.parser = parser or (lambda: None) + self.parser = parser or custom_date_parser def get_completions( self, document: Document, complete_event: CompleteEvent From 67a21dc6af6a26a5ba05b8732b03f5c5961d9676 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:11:39 +0100 Subject: [PATCH 22/48] :white_check_mark: date parser in testsuite is no longer required --- tests/prompts/test_date.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 4e6233e6..681eed1d 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Testsuite for date module.""" -from typing import List, Optional +from typing import List import datetime @@ -60,15 +60,6 @@ def _returns_expected_completions( ) -def custom_date_parser(input: str) -> Optional[datetime.date]: - """Some date parser.""" - try: - _date = datetime.datetime.strptime(input, "%Y-%m-%d %H:%M") - except Exception: - _date = None - return _date - - def test_simple_date_completer(): """Yields completions depending on the chosen ``date_format``.""" # check order of completions @@ -110,6 +101,11 @@ def test_simple_date_validator_exception(): date.SimpleDateValidator(date_format="not-supported") +def test_parsing_completer(): + """Completion using a custom date parser.""" + completer = date.ParsingDateCompleter(parser=date.custom_date_parser) + + def test_date(): """Test date on default behavior.""" message = "Type a date: " From f5985e4a135942dea8e63ca85c157622579301e1 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:18:28 +0100 Subject: [PATCH 23/48] :bug: corrected regex in custom_date_parser --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 4475959a..5818e0ff 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -145,7 +145,7 @@ def _try_date_format(date_format:str, text:str) -> Optional[datetime.datetime]: return None # remove all delimeters from the input - pattern = re.compile("[\d]") + pattern = re.compile(r"[\d]") relevant_input = "".join(pattern.findall(input)) # try parsing for the several date_formats for date_format in date_formats: From 9af003d5eedc59eb1046e8754734d7387323ed70 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:32:30 +0100 Subject: [PATCH 24/48] :zap: add microseconds to 'custom_date_parser' --- questionary/prompts/date.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 5818e0ff..8be2ca07 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -131,7 +131,8 @@ def custom_date_parser(input: str) -> Optional[datetime.date]: "%d", "%H", "%M", - "%S" + "%S", + "%f" ] date_formats = [ "".join(_time_format_codes[0:i]) for i in range(len(_time_format_codes)) From 11d5146ec2561bdbb55990f0f7f402d0b23a694d Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:33:37 +0100 Subject: [PATCH 25/48] :white_check_mark: add test for 'ParsingDateCompletion' --- tests/prompts/test_date.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 681eed1d..0ec2bebf 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -104,6 +104,9 @@ def test_simple_date_validator_exception(): def test_parsing_completer(): """Completion using a custom date parser.""" completer = date.ParsingDateCompleter(parser=date.custom_date_parser) + input = document.Document("2021") + completions = [c.text for c in completer.get_completions(input, CompleteEvent())] + assert "2021-01-01 00:00:00" in completions def test_date(): From e3c530c9b4ff7e993fab8f67f4adf90a3f38bf5b Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:39:14 +0100 Subject: [PATCH 26/48] :zap: check type of 'parser' --- questionary/prompts/date.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 8be2ca07..8de68c47 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -43,6 +43,7 @@ import re import datetime +from attr.validators import is_callable from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.completion import Completer @@ -299,10 +300,15 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. the ones from `dateparser`_ or `dateutil`_. Defaults to None. + Raises: + ValueError: if ``parser`` is neither None nor a callable. + .. _dateparser: https://github.com/scrapinghub/dateparser .. _dateutil: https://github.com/dateutil/dateutil """ self.parser = parser or custom_date_parser + if not is_callable(self.parser): + raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) def get_completions( self, document: Document, complete_event: CompleteEvent @@ -343,10 +349,15 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. the ones from `dateparser`_ or `dateutil`_. Defaults to None. + Raises: + ValueError: if ``parser`` is neither None nor a callable + .. _dateparser: https://github.com/scrapinghub/dateparser .. _dateutil: https://github.com/dateutil/dateutil """ - self.parser = parser + self.parser = parser or custom_date_parser + if not is_callable(self.parser): + raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) def validate(self, document: Document) -> None: """Validates the date input using a date parser. From ad4b29e9a9e53b86fcf21daba0432fff35ba5fa9 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:58:42 +0100 Subject: [PATCH 27/48] :bug:fix creation of 'date_formats' --- questionary/prompts/date.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 8de68c47..118d751d 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -43,7 +43,6 @@ import re import datetime -from attr.validators import is_callable from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.completion import Completer @@ -136,7 +135,7 @@ def custom_date_parser(input: str) -> Optional[datetime.date]: "%f" ] date_formats = [ - "".join(_time_format_codes[0:i]) for i in range(len(_time_format_codes)) + "".join(_time_format_codes[0:i]) for i in range(1, len(_time_format_codes) + 1) ] def _try_date_format(date_format:str, text:str) -> Optional[datetime.datetime]: @@ -307,7 +306,7 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: .. _dateutil: https://github.com/dateutil/dateutil """ self.parser = parser or custom_date_parser - if not is_callable(self.parser): + if not isinstance(self.parser, Callable): raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) def get_completions( @@ -356,7 +355,7 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: .. _dateutil: https://github.com/dateutil/dateutil """ self.parser = parser or custom_date_parser - if not is_callable(self.parser): + if not isinstance(self.parser, Callable): raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) def validate(self, document: Document) -> None: From b803be8f4f76c0ce869b1d01e820b6f38f4f8072 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 16:59:24 +0100 Subject: [PATCH 28/48] :white_check_mark: add test for 'ParsingDateValidator' --- tests/prompts/test_date.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 0ec2bebf..d32d3497 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -5,6 +5,7 @@ import datetime import prompt_toolkit +from prompt_toolkit.validation import ValidationError import pytest from prompt_toolkit import document from prompt_toolkit.completion.base import CompleteEvent @@ -109,6 +110,33 @@ def test_parsing_completer(): assert "2021-01-01 00:00:00" in completions +def test_parsing_completer_exception(): + """Parser needs to be a callable or None.""" + date.ParsingDateCompleter() + + with pytest.raises(ValueError): + date.ParsingDateCompleter(parser="i am not a callable") + + +def test_parsing_validator(): + """Validaton using a custom date parser.""" + validator = date.ParsingDateValidator(parser=date.custom_date_parser) + input = document.Document("2021") + validator.validate(input) + + # raises if date cannot be validated + with pytest.raises(ValidationError): + validator.validate(document.Document("invalid")) + + +def test_parsing_validator_init_exception(): + """Parser needs to be a callable or None.""" + date.ParsingDateValidator() + + with pytest.raises(ValueError): + date.ParsingDateValidator(parser="i am not a callable") + + def test_date(): """Test date on default behavior.""" message = "Type a date: " From 77b5ee83e22e466626c227d1d431b9c423c503d2 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 21:27:19 +0100 Subject: [PATCH 29/48] :zap: default parser for 'date' is no 'custom_date_parser' --- questionary/prompts/date.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 118d751d..78b836b0 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -557,7 +557,8 @@ def date( right of the prompt. Defaults to True. parser (Callable[[str], AnyDate], optional): A callable that parses a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. - the ones from `dateparser`_ or `dateutil`_. Defaults to None. + the ones from `dateparser`_ or `dateutil`_. If None, set to + :function: `custom_date_parser`.Defaults to None. return_date_object (bool): If True, a parsed date object is returned. Else, string the text input is returned. Defaults to True. complete_style (CompleteStyle): How autocomplete menu would be shown, it could @@ -580,6 +581,8 @@ def date( # delimeter used to separate year, month and day delimeter: str = date_format[-3] + parser = parser or custom_date_parser + merged_style = merge_styles([DEFAULT_STYLE, style]) if print_date_format: From 0bb9196f6ca82decbf11fc5e60a6d8a3609fa0f9 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 22:03:01 +0100 Subject: [PATCH 30/48] :zap: add parameter for deactivating external parser --- questionary/prompts/date.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 78b836b0..c67d2adf 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -1,19 +1,21 @@ """Module for prompting for dates. -This module provides a prompting method for dates and times. Two basic types of -completion and validation are provided: :class: `SimpleDateCompleter` and :class: +Core of this module forms the function :function: `date`, which allows to prompt for for +dates and times with completion and validation. Two basic types of completion and +validation are provided: :class: `SimpleDateCompleter` and :class: `ParsingDateCompleter`. The former one, using only the build in module :module: -`datetime`, whereas the latter one offers the option to easily include third part libraries, e.g. -as `dateutil`_ or `dateparser`_, for completion. Similar class are there for validation -(:class: `SimpleDateValidator` and :class: `ParsingDateValidator`). Note that the -'simple' completer and validator currently only supports dates or times, but not both at -the same time. The 'parsing' ones, on the other hand, may support times as well -(depending on the used third part library). Both ``Completer`` and ``Validator``, the -'simple' and the 'parsing' ones are combined in :class: `FullDateCompleter` -and :class: `FullDateValidator`. If no parser is given to the 'full' completer and +`datetime`, whereas the latter one offers the option to easily include third part +libraries, e.g. as `dateutil`_ or `dateparser`_, for completion. Similar class are there +for validation (:class: `SimpleDateValidator` and :class: `ParsingDateValidator`). Note +that the 'simple' completer and validator currently only supports dates or times, but +not both at the same time. The 'parsing' ones, on the other hand, may support times as +well (depending on the used third part library). Both ``Completer`` and ``Validator``, +the 'simple' and the 'parsing' ones are combined in :class: `FullDateCompleter` and +:class: `FullDateValidator`. If no parser is given to the 'full' completer and validator, :function: `custom_date_parser` is used by default. If you want to deactivate -'parsing' validation and completion, ``parser`` needs to be (lambda: None). +'parsing' validation and completion, you need to call :function: `date` with +``no_extra_parser`` set to True. By default date returns an :class: `datetime.datetime` instance. @@ -445,6 +447,8 @@ def get_completions( parsed_completions = ( self.parsing_completer.get_completions(document, complete_event) or [] ) + if self.parser is None: + parsed_completions = [] for completion in simple_completions: yield completion for completion in parsed_completions: @@ -527,6 +531,7 @@ def date( date_format: Optional[str] = ISO8601, print_date_format: bool = True, parser: Optional[Callable[[str], AnyDate]] = None, + no_extra_parser: bool = False, return_date_object: bool = True, complete_style: CompleteStyle = CompleteStyle.MULTI_COLUMN, **kwargs: Any, @@ -558,7 +563,10 @@ def date( parser (Callable[[str], AnyDate], optional): A callable that parses a string into a :class: `datetime.date` or :class: `datetime.date`, e.g. the ones from `dateparser`_ or `dateutil`_. If None, set to - :function: `custom_date_parser`.Defaults to None. + :function: `custom_date_parser`. Defaults to None. + Has no effect is ``no_extra_parser`` is set to True. + no_extra_parser (bool): If True, completion and validation using the ``parser`` is + not performed. Defaults to False. return_date_object (bool): If True, a parsed date object is returned. Else, string the text input is returned. Defaults to True. complete_style (CompleteStyle): How autocomplete menu would be shown, it could @@ -582,6 +590,9 @@ def date( delimeter: str = date_format[-3] parser = parser or custom_date_parser + if no_extra_parser: + parser = None + merged_style = merge_styles([DEFAULT_STYLE, style]) From 4c035381100e07af0873f1998e98f67fd4a6e168 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 22:06:29 +0100 Subject: [PATCH 31/48] :art: run black on code --- questionary/prompts/date.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index c67d2adf..1cad7942 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -77,9 +77,6 @@ ISO8601_TIME, "%d.%m.%Y", "%d-%m-%Y", - # ISO8601, - # "%d.%m.%y", - # "%d-%m-%y", "%m-%d-%Y", "%m/%d/%Y", "%m.%d.%Y", @@ -94,7 +91,14 @@ SECOND = [str(i) for i in range(60)] # dict used to determine correct order for completions -PARSE_FORMAT_DICT = {"%d": DAY, "%m": MONTH, "%Y": YEAR, "%H": HOUR, "%M": MINUTE, "%S": SECOND} +PARSE_FORMAT_DICT = { + "%d": DAY, + "%m": MONTH, + "%Y": YEAR, + "%H": HOUR, + "%M": MINUTE, + "%S": SECOND, +} AnyDate = Union[datetime.date, datetime.datetime, None] @@ -127,20 +131,12 @@ def custom_date_parser(input: str) -> Optional[datetime.date]: .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 """ - _time_format_codes = [ - "%Y", - "%m", - "%d", - "%H", - "%M", - "%S", - "%f" - ] + _time_format_codes = ["%Y", "%m", "%d", "%H", "%M", "%S", "%f"] date_formats = [ "".join(_time_format_codes[0:i]) for i in range(1, len(_time_format_codes) + 1) ] - def _try_date_format(date_format:str, text:str) -> Optional[datetime.datetime]: + def _try_date_format(date_format: str, text: str) -> Optional[datetime.datetime]: """Tries to parse ``text`to :class: `datetime.datetime`.""" try: return datetime.datetime.strptime(text, date_format) @@ -152,10 +148,7 @@ def _try_date_format(date_format:str, text:str) -> Optional[datetime.datetime]: relevant_input = "".join(pattern.findall(input)) # try parsing for the several date_formats for date_format in date_formats: - _date = _try_date_format( - date_format=date_format, - text=relevant_input - ) + _date = _try_date_format(date_format=date_format, text=relevant_input) # if parsing succeeded return the result if _date is not None: return _date @@ -309,7 +302,11 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: """ self.parser = parser or custom_date_parser if not isinstance(self.parser, Callable): - raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) + raise ( + ValueError( + f"'parser' needs to be a callable (not a {type(parser).__name__})." + ) + ) def get_completions( self, document: Document, complete_event: CompleteEvent @@ -358,7 +355,11 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: """ self.parser = parser or custom_date_parser if not isinstance(self.parser, Callable): - raise(ValueError(f"'parser' needs to be a callable (not a {type(parser).__name__}).")) + raise ( + ValueError( + f"'parser' needs to be a callable (not a {type(parser).__name__})." + ) + ) def validate(self, document: Document) -> None: """Validates the date input using a date parser. @@ -593,7 +594,6 @@ def date( if no_extra_parser: parser = None - merged_style = merge_styles([DEFAULT_STYLE, style]) if print_date_format: From 8a823054dccf71a933aa869b38ba498ce40d9ac6 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 1 Nov 2021 22:22:03 +0100 Subject: [PATCH 32/48] :bug: 'date_format' could not be set to None --- questionary/prompts/date.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 1cad7942..245ee4e0 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -588,7 +588,10 @@ def date( .. _dateutil: https://github.com/dateutil/dateutil """ # delimeter used to separate year, month and day - delimeter: str = date_format[-3] + if isinstance(date_format, str): + delimeter: str = date_format[-3] + else: + delimeter = " " parser = parser or custom_date_parser if no_extra_parser: From fe8922757e001e2f507aee0d1829e78ba742df39 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Tue, 2 Nov 2021 09:56:44 +0100 Subject: [PATCH 33/48] :white_check_mark: add several tests --- tests/prompts/test_date.py | 126 ++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index d32d3497..301901f0 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -35,7 +35,7 @@ def _check_simple_completions( if len(text_inputs) != len(order): raise ( ValueError( - "'tect_inputs' and 'order' need to have the same number of items." + "'text_inputs' and 'order' need to have the same number of items." ) ) @@ -137,6 +137,50 @@ def test_parsing_validator_init_exception(): date.ParsingDateValidator(parser="i am not a callable") +def test_full_completer(): + """Completion using a custom date parser and 'simple' method.""" + completer = date.FullDateCompleter(parser=date.custom_date_parser) + input = document.Document("2021") + completions = [c.text for c in completer.get_completions(input, CompleteEvent())] + assert "2021-01-01 00:00:00" in completions + + +def test_full_completer_deactivations(): + """Parser or 'simple' one can be deactivated.""" + # deactivate 'ParsingDateCompleter' + completer = date.FullDateCompleter(parser=None) + input = document.Document("2021") + completions = [c.text for c in completer.get_completions(input, CompleteEvent())] + assert "2021-01-01 00:00:00" not in completions + + # deactivate 'SimpleDateCompleter' + completer = date.FullDateCompleter(date_format=None) + input = document.Document("2021") + completions = [c.text for c in completer.get_completions(input, CompleteEvent())] + assert "2021-" not in completions + + +def test_full_validator(): + """Validaton using a custom date parser and 'simple' method.""" + validator = date.FullDateValidator(parser=date.custom_date_parser) + input = document.Document("2021") + validator.validate(input) + + # raises if date cannot be validated + with pytest.raises(ValidationError): + validator.validate(document.Document("invalid")) + + +def test_full_validator_parser_deactivated(): + """Validaton using a custom date parser and 'simple' method.""" + validator = date.FullDateValidator(parser=None) + input = document.Document("2021") + + # raises if date cannot be validated + with pytest.raises(ValidationError): + validator.validate(input) + + def test_date(): """Test date on default behavior.""" message = "Type a date: " @@ -151,3 +195,83 @@ def test_date_string_return(): text = "2021-01-01" + KeyInputs.ENTER result, cli = feed_cli_with_input("date", message, text, return_date_object=False) assert result == "2021-01-01" + + +@pytest.mark.skipif( + prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" +) +def test_complete_date(): + test_input = "202" + message = "Type a date: " + texts = [ + test_input, + KeyInputs.TAB + KeyInputs.TAB + KeyInputs.ENTER, + KeyInputs.ENTER, + ] + + result, cli = feed_cli_with_input("date", message, texts, 0.1) + assert result == datetime.datetime(2021, 1, 1, 0, 0) + + +@pytest.mark.skipif( + prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" +) +def test_complete_date(): + """Date has a completer.""" + test_input = "202" + message = "Type a date: " + texts = [ + test_input, + KeyInputs.TAB + KeyInputs.TAB + KeyInputs.ENTER, + KeyInputs.ENTER, + ] + + result, cli = feed_cli_with_input("date", message, texts, 0.1) + assert result == datetime.datetime(2021, 1, 1, 0, 0) + + +def test_date_print_date_format_to_right(): + """``date_format`` can be printed to the right or not.""" + message = "Type a date: " + text = "2021-01-01" + KeyInputs.ENTER + result, cli = feed_cli_with_input("date", message, text, print_date_format=False) + + +@pytest.mark.skipif( + prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" +) +def test_complete_date_no_parser(): + """'Parsing' validation and completion can be deactivated. + + :class: `ParsingDateCompleter` and :class: `ParsingDateValidator` can be + deactivated. + """ + test_input = "202" + message = "Type a date: " + texts = [ + test_input, + KeyInputs.TAB + KeyInputs.TAB + KeyInputs.ENTER, + "1", + KeyInputs.TAB + KeyInputs.ENTER, + "1", + KeyInputs.TAB + KeyInputs.ENTER, + KeyInputs.ENTER, + ] + result, cli = feed_cli_with_input("date", message, texts, 0.1, no_extra_parser=True) + assert result == datetime.datetime(2021, 1, 1, 0, 0) + + +@pytest.mark.skipif( + prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" +) +def test_complete_no_simple_date_validation_and_completion(): + """'Simple' validation and completion can be deactivated. + + :class: `SimpleDateCompleter` and :class: `SimpleDateValidator` can be + deactivated. + """ + test_input = "2021" + message = "Type a date: " + texts = [test_input, KeyInputs.TAB + KeyInputs.ENTER, KeyInputs.ENTER] + result, cli = feed_cli_with_input("date", message, texts, 0.1, date_format=None) + assert result == datetime.datetime(2021, 1, 1, 0, 0) From 8abea726839b78b567a8e075c2b4e6f0226b1aed Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Tue, 2 Nov 2021 10:43:27 +0100 Subject: [PATCH 34/48] :rotating_light: fixed mypy warnings --- questionary/prompts/date.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 245ee4e0..1d8925ff 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -184,14 +184,14 @@ def __init__(self, date_format: Optional[str] = None) -> None: ) self.delimeter: str = self.format[-3] - def _get_parse_order(self) -> List[str]: + def _get_parse_order(self) -> List[List[str]]: """Returns the order for completions. Parses ``self.date_format`` into a list representing the order for completions, e.g. [YEAR, MONTH, DAY]. """ parse_order = self.format.split(self.delimeter) - return [PARSE_FORMAT_DICT.get(item) for item in parse_order] + return [PARSE_FORMAT_DICT.get(item) for item in parse_order] # type: ignore[misc] def get_completions( self, document: Document, complete_event: CompleteEvent @@ -301,7 +301,7 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: .. _dateutil: https://github.com/dateutil/dateutil """ self.parser = parser or custom_date_parser - if not isinstance(self.parser, Callable): + if not callable(self.parser): raise ( ValueError( f"'parser' needs to be a callable (not a {type(parser).__name__})." @@ -354,7 +354,7 @@ def __init__(self, parser: Optional[Callable[[str], AnyDate]] = None) -> None: .. _dateutil: https://github.com/dateutil/dateutil """ self.parser = parser or custom_date_parser - if not isinstance(self.parser, Callable): + if not callable(self.parser): raise ( ValueError( f"'parser' needs to be a callable (not a {type(parser).__name__})." @@ -609,12 +609,14 @@ def date( def get_prompt_tokens() -> List[Tuple[str, str]]: return [("class:qmark", qmark), ("class:question", " {} ".format(message))] - def _parse_to_date(input: str) -> datetime.datetime: + def _parse_to_date( + input: str, + ) -> Union[str, datetime.datetime, datetime.date, None]: if return_date_object: if parser is not None: return parser(input) else: - return datetime.datetime.strptime(input, date_format) + return datetime.datetime.strptime(input, date_format) # type: ignore[arg-type] else: return input From 27a0dae688285f92a1de6f6311b346fadfdc264c Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Tue, 2 Nov 2021 11:21:53 +0100 Subject: [PATCH 35/48] :memo: fix import in example --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 1d8925ff..9977e787 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -23,7 +23,7 @@ Example: >>> import questionary - >>> import dateutil + >>> import dateutil.parser >>> questionary.date("Type a date: ").ask() ? Type a date: 2021-01-01 '2021-01-01' From 85608e56b30c37a0217ad76a2c5372e239949a8d Mon Sep 17 00:00:00 2001 From: NicDom <83905171+NicDom@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:49:51 +0100 Subject: [PATCH 36/48] Update README.md Co-authored-by: Kian Cross --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f7a60160..53a50818 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,14 @@ ✨ Questionary is a Python library for effortlessly building pretty command line interfaces ✨ -- [Questionary](#questionary) - - [Features](#features) - - [Installation](#installation) - - [Usage](#usage) - - [Documentation](#documentation) - - [Support](#support) - - [Contributing](#contributing) - - [Authors and Acknowledgment](#authors-and-acknowledgment) - - [License](#license) +[Features](#features) +[Installation](#installation) +[Usage](#usage) +[Documentation](#documentation) +[Support](#support) +[Contributing](#contributing) +[Authors and Acknowledgment](#authors-and-acknowledgment) +[License](#license) ![Example](https://raw.githubusercontent.com/tmbo/questionary/master/docs/images/example.gif) From a3f414f47325de43a38492b2967c9c80b6330578 Mon Sep 17 00:00:00 2001 From: NicDom <83905171+NicDom@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:50:03 +0100 Subject: [PATCH 37/48] Update questionary/prompts/date.py Fix typo Co-authored-by: Kian Cross --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 9977e787..0b42ec58 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -198,7 +198,7 @@ def get_completions( ) -> Iterable[Completion]: """Completion of text input following a given ``date_format``. - According what has allready been typed and the given ``date_format`` + According what has already been typed and the given ``date_format`` completions for year, month and day are given (numbers only). Date format ``date_format`` needs to be one of the ``SUPPORTED_FORMATS´´. Supports only dates at the moment. From 6ae8be2d31a08c0201dbc73c07ef4734b10f7f25 Mon Sep 17 00:00:00 2001 From: NicDom <83905171+NicDom@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:50:18 +0100 Subject: [PATCH 38/48] Update questionary/prompts/date.py Fix typo Co-authored-by: Kian Cross --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 0b42ec58..d313b5e9 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -157,7 +157,7 @@ def _try_date_format(date_format: str, text: str) -> Optional[datetime.datetime] ################################### -# SIMPLE COMLETION AND VALIDATION +# SIMPLE COMPLETION AND VALIDATION ################################### From d327fb3e477e36a37a45ddf036e548acce45bbc5 Mon Sep 17 00:00:00 2001 From: NicDom <83905171+NicDom@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:50:28 +0100 Subject: [PATCH 39/48] Update questionary/prompts/date.py Fix typo Co-authored-by: Kian Cross --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index d313b5e9..15ee62d9 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -279,7 +279,7 @@ def validate(self, document: Document) -> None: ################################### -# PARSING COMLETION AND VALIDATION +# PARSING COMPLETION AND VALIDATION ################################### From d0644eaca955bea3f6983035d88ab888a512b4de Mon Sep 17 00:00:00 2001 From: NicDom <83905171+NicDom@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:50:35 +0100 Subject: [PATCH 40/48] Update questionary/prompts/date.py Fix typo Co-authored-by: Kian Cross --- questionary/prompts/date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 15ee62d9..1977ed12 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -385,7 +385,7 @@ def validate(self, document: Document) -> None: ################################### -# FULL COMLETION AND VALIDATION +# FULL COMPLETION AND VALIDATION ################################### From 2f73e98b3b84d0e6481cdb7aa99459cb406e860e Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Fri, 24 Dec 2021 10:21:56 +0100 Subject: [PATCH 41/48] :children_crossing: Add date to __all__ --- questionary/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/questionary/__init__.py b/questionary/__init__.py index 649e122c..ca681b48 100644 --- a/questionary/__init__.py +++ b/questionary/__init__.py @@ -17,6 +17,7 @@ from questionary.prompts.checkbox import checkbox from questionary.prompts.text import text from questionary.prompts.path import path +from questionary.prompts.date import date from questionary.prompts.confirm import confirm from questionary.prompts.password import password from questionary.prompts.rawselect import rawselect @@ -31,6 +32,7 @@ "checkbox", "confirm", "password", + "date", "path", "rawselect", "select", From 77cc28ada0eec7f02b3d6e01094836b9c75e8e2c Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Fri, 24 Dec 2021 10:23:00 +0100 Subject: [PATCH 42/48] :sparkles: Completion only shows completions for valid dates Completions are checked, if the lead to a valid date --- questionary/prompts/date.py | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 1977ed12..9f815105 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -193,6 +193,45 @@ def _get_parse_order(self) -> List[List[str]]: parse_order = self.format.split(self.delimeter) return [PARSE_FORMAT_DICT.get(item) for item in parse_order] # type: ignore[misc] + def _valid_completion(self, completion: str) -> bool: + """Checks if a completion is a valid date. + + Used in :function: `get_completion` to reduce all options for completions + to only those that lead to a valid date. + Similar to :function: `custom_date_parser`, but does not ignore delimeters. + + Args: + completion (str): The completion-option that is to be checked, if it + leads to an valid date. + """ + format_as_list = self.format.split(self.delimeter) + date_formats = [ + self.delimeter.join(format_as_list[0:i]) + for i in range(1, len(format_as_list) + 1) + ] + + # add delimeter to all but the last entry + for i in range(len(date_formats) - 1): + date_formats[i] += self.delimeter + + def _try_date_format( + date_format: str, text: str + ) -> Optional[datetime.datetime]: + """Tries to parse ``text`to :class: `datetime.datetime`.""" + try: + return datetime.datetime.strptime(text, date_format) + except Exception: + return None + + # try parsing for the several date_formats + for date_format in date_formats: + _date = _try_date_format(date_format=date_format, text=completion) + # if parsing succeeded return True + if _date is not None: + return True + # no parsing succeeded so return False + return False + def get_completions( self, document: Document, complete_event: CompleteEvent ) -> Iterable[Completion]: @@ -227,8 +266,11 @@ def get_completions( if entry.startswith(user_input): completion = entry if int(entry) >= 10 else "0" + entry completion += self.delimeter if len(text_split) < 3 else "" + full_output = old_input + completion + # if completion leads to a valid date, yield this completion + if self._valid_completion(full_output): yield Completion( - old_input + completion, + full_output, start_position=-len(document.text), style="fg:ansigreen bold", selected_style="fg:white bg:ansired bold", From e82c0a410d8f9c1de53ce5f057a8085842d2de13 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 12:45:09 +0100 Subject: [PATCH 43/48] =?UTF-8?q?:bug:`full=5Foutput=C2=B4=20was=20used=20?= =?UTF-8?q?before=20referenced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- questionary/prompts/date.py | 1 + 1 file changed, 1 insertion(+) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index 9f815105..aa1813ed 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -263,6 +263,7 @@ def get_completions( completion_list = self._get_parse_order()[len(text_split) - 1] # find fitting completions in completion_list for entry in completion_list: + full_output = entry if entry.startswith(user_input): completion = entry if int(entry) >= 10 else "0" + entry completion += self.delimeter if len(text_split) < 3 else "" From 8bfdc6b8ed80747c8ce856dfe987ae90c8f03865 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 12:45:51 +0100 Subject: [PATCH 44/48] :white_check_mark: now only valid dates are yield by completion --- tests/prompts/test_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 301901f0..6a2526f2 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -67,7 +67,7 @@ def test_simple_date_completer(): _check_simple_completions( date_format=date.ISO8601, text_inputs=["202", "2021-02", "2021-02-02"], - order=[date.YEAR, date.MONTH, date.DAY], + order=[date.YEAR, date.MONTH, date.DAY[0:28]], # February has only 28 days 2021 delimeter="-", ) From 8200ce173b772b3bfa3a51ffe325f9d450873187 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 12:47:58 +0100 Subject: [PATCH 45/48] :sparkles: introduce new styles for autocomplete options and information at lower right --- questionary/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/questionary/constants.py b/questionary/constants.py index 9aab0d73..b36a11a7 100644 --- a/questionary/constants.py +++ b/questionary/constants.py @@ -45,5 +45,7 @@ ("instruction", ""), # user instructions for select, rawselect, checkbox ("text", ""), # any other text ("instruction", ""), # user instructions for select, rawselect, checkbox + ("options", ""), # options given by autocomplete + ("information", ""), # information displayed at the lower right ] ) From fba0ede4d492f893fc85022858b3d94ea6b00ec9 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 13:02:06 +0100 Subject: [PATCH 46/48] :white_check_mark: test_complete_date appeared twice --- tests/prompts/test_date.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/prompts/test_date.py b/tests/prompts/test_date.py index 6a2526f2..d6eba5f5 100644 --- a/tests/prompts/test_date.py +++ b/tests/prompts/test_date.py @@ -197,22 +197,6 @@ def test_date_string_return(): assert result == "2021-01-01" -@pytest.mark.skipif( - prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" -) -def test_complete_date(): - test_input = "202" - message = "Type a date: " - texts = [ - test_input, - KeyInputs.TAB + KeyInputs.TAB + KeyInputs.ENTER, - KeyInputs.ENTER, - ] - - result, cli = feed_cli_with_input("date", message, texts, 0.1) - assert result == datetime.datetime(2021, 1, 1, 0, 0) - - @pytest.mark.skipif( prompt_toolkit.__version__.startswith("2"), reason="requires prompt toolkit >= 3.0" ) From 6df6ce0d301f795c067daa0fcef1b1dfe54da05d Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 15:18:44 +0100 Subject: [PATCH 47/48] :sparkles: define default style for 'information' and 'options' --- questionary/constants.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/questionary/constants.py b/questionary/constants.py index b36a11a7..df11d434 100644 --- a/questionary/constants.py +++ b/questionary/constants.py @@ -45,7 +45,10 @@ ("instruction", ""), # user instructions for select, rawselect, checkbox ("text", ""), # any other text ("instruction", ""), # user instructions for select, rawselect, checkbox - ("options", ""), # options given by autocomplete - ("information", ""), # information displayed at the lower right + ("options", "fg: ansigreen bold"), # options given by autocomplete + ( + "information", + "bg: ansigreen bold", + ), # information displayed at the lower right ] ) From 6beee398bf518b4237f757d05d02cb481a18eb50 Mon Sep 17 00:00:00 2001 From: Niclas Gesing Date: Mon, 27 Dec 2021 15:19:39 +0100 Subject: [PATCH 48/48] :sparkles: use classes for styles of complete options to make them customizable --- questionary/prompts/date.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/questionary/prompts/date.py b/questionary/prompts/date.py index aa1813ed..8371b646 100644 --- a/questionary/prompts/date.py +++ b/questionary/prompts/date.py @@ -273,8 +273,8 @@ def get_completions( yield Completion( full_output, start_position=-len(document.text), - style="fg:ansigreen bold", - selected_style="fg:white bg:ansired bold", + style="class:options", + selected_style="class:selected", display=completion, ) @@ -374,7 +374,12 @@ def get_completions( parsed_date = None # if input can be parsed, yield the string of the parsed date as completion if parsed_date is not None: - yield Completion(str(parsed_date), start_position=-len(document.text)) + yield Completion( + str(parsed_date), + start_position=-len(document.text), + style="class:options", + selected_style="class:selected", + ) class ParsingDateValidator(Validator): @@ -640,11 +645,15 @@ def date( if no_extra_parser: parser = None - merged_style = merge_styles([DEFAULT_STYLE, style]) + # define a default style for selected completions. Gets overwritten if another + # format for selected is given by `style` + default_date_styles = Style([("selected", "fg: white bg: ansired bold")]) + + merged_style = merge_styles([DEFAULT_STYLE, default_date_styles, style]) if print_date_format: rprompt = to_formatted_text( - f"Date format: {date_format}", style="bg: ansigreen bold" + f"Date format: {date_format}", style="class:information" ) else: rprompt = None