diff --git a/.gitignore b/.gitignore index 93c328e..8376004 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ __pycache__ *.pyc +.hypothesis dask-worker-space .ipynb_checkpoints diff --git a/poetry.lock b/poetry.lock index af1e251..962de9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -138,6 +138,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "attrs" version = "21.4.0" @@ -353,7 +361,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.10" +version = "2.0.11" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -479,7 +487,7 @@ python-versions = ">=3.6" [[package]] name = "dask" -version = "2022.1.0" +version = "2022.1.1" description = "Parallel PyData with Task Scheduling" category = "main" optional = false @@ -488,7 +496,7 @@ python-versions = ">=3.7" [package.dependencies] bokeh = {version = ">=2.1.1", optional = true, markers = "extra == \"complete\""} cloudpickle = ">=1.1.1" -distributed = {version = "2022.01.0", optional = true, markers = "extra == \"complete\""} +distributed = {version = "2022.01.1", optional = true, markers = "extra == \"complete\""} fsspec = ">=0.6.0" jinja2 = {version = "*", optional = true, markers = "extra == \"complete\""} numpy = {version = ">=1.18", optional = true, markers = "extra == \"array\""} @@ -500,10 +508,10 @@ toolz = ">=0.8.2" [package.extras] array = ["numpy (>=1.18)"] -complete = ["bokeh (>=2.1.1)", "distributed (==2022.01.0)", "jinja2", "numpy (>=1.18)", "pandas (>=1.0)"] +complete = ["bokeh (>=2.1.1)", "distributed (==2022.01.1)", "jinja2", "numpy (>=1.18)", "pandas (>=1.0)"] dataframe = ["numpy (>=1.18)", "pandas (>=1.0)"] diagnostics = ["bokeh (>=2.1.1)", "jinja2"] -distributed = ["distributed (==2022.01.0)"] +distributed = ["distributed (==2022.01.1)"] test = ["pytest", "pytest-rerunfailures", "pytest-xdist", "pre-commit"] [[package]] @@ -546,7 +554,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "distributed" -version = "2022.1.0" +version = "2022.1.1" description = "Distributed scheduler for Dask" category = "main" optional = false @@ -555,7 +563,7 @@ python-versions = ">=3.7" [package.dependencies] click = ">=6.6" cloudpickle = ">=1.5.0" -dask = "2022.01.0" +dask = "2022.01.1" jinja2 = "*" msgpack = ">=0.6.0" packaging = ">=20.0" @@ -612,14 +620,14 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "fonttools" -version = "4.28.5" +version = "4.29.0" description = "Tools to manipulate font files" category = "main" optional = true python-versions = ">=3.7" [package.extras] -all = ["fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "zopfli (>=0.1.4)", "lz4 (>=1.7.4.2)", "matplotlib", "sympy", "skia-pathops (>=0.5.0)", "brotlicffi (>=0.8.0)", "scipy", "brotli (>=1.0.1)", "munkres", "unicodedata2 (>=13.0.0)", "xattr"] +all = ["fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "zopfli (>=0.1.4)", "lz4 (>=1.7.4.2)", "matplotlib", "sympy", "skia-pathops (>=0.5.0)", "brotlicffi (>=0.8.0)", "scipy", "brotli (>=1.0.1)", "munkres", "unicodedata2 (>=14.0.0)", "xattr"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["scipy", "munkres"] lxml = ["lxml (>=4.0,<5)"] @@ -628,7 +636,7 @@ plot = ["matplotlib"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=13.0.0)"] +unicode = ["unicodedata2 (>=14.0.0)"] woff = ["zopfli (>=0.1.4)", "brotlicffi (>=0.8.0)", "brotli (>=1.0.1)"] [[package]] @@ -725,6 +733,34 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "hypothesis" +version = "6.36.1" +description = "A library for property-based testing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2021.5)"] +cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=2.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.25)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2021.5)"] + [[package]] name = "idna" version = "3.3" @@ -772,6 +808,14 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "ipykernel" version = "6.7.0" @@ -946,7 +990,7 @@ format_nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jupyter-client" -version = "7.1.1" +version = "7.1.2" description = "Jupyter protocol implementation and client libraries" category = "main" optional = false @@ -995,7 +1039,7 @@ dev = ["autopep8", "black", "pytest", "flake8", "pytest-cov (>=2.6.1)", "mock"] [[package]] name = "jupyter-server" -version = "1.13.3" +version = "1.13.4" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." category = "main" optional = false @@ -1024,7 +1068,7 @@ test = ["coverage", "pytest (>=6.0)", "pytest-cov", "pytest-mock", "requests", " [[package]] name = "jupyter-server-proxy" -version = "3.2.0" +version = "3.2.1" description = "Jupyter server extension to supervise and proxy web services" category = "main" optional = false @@ -1273,11 +1317,11 @@ python-versions = "*" [[package]] name = "multidict" -version = "5.2.0" +version = "6.0.2" description = "multidict implementation" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mypy-extensions" @@ -1322,7 +1366,7 @@ test = ["ipython", "ipykernel", "ipywidgets (<8.0.0)", "pytest (>=4.1)", "pytest [[package]] name = "nbconvert" -version = "6.4.0" +version = "6.4.1" description = "Converting Jupyter Notebooks" category = "main" optional = false @@ -1394,7 +1438,7 @@ python-versions = ">=3.5" [[package]] name = "notebook" -version = "6.4.7" +version = "6.4.8" description = "A web-based notebook environment for interactive computing" category = "main" optional = false @@ -1432,18 +1476,18 @@ python-versions = ">=3.8" [[package]] name = "numpydoc" -version = "1.1.0" +version = "1.2" description = "Sphinx extension to support docstrings in Numpy format" category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.7" [package.dependencies] -Jinja2 = ">=2.3" -sphinx = ">=1.6.5" +Jinja2 = ">=2.10" +sphinx = ">=1.8" [package.extras] -testing = ["matplotlib", "pytest", "pytest-cov"] +testing = ["pytest", "pytest-cov", "matplotlib"] [[package]] name = "packaging" @@ -1598,6 +1642,18 @@ python-versions = ">=3.7" docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "plumbum" version = "1.7.2" @@ -1624,18 +1680,18 @@ python-versions = "*" [[package]] name = "prometheus-client" -version = "0.12.0" +version = "0.13.1" description = "Python client for the Prometheus monitoring system." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.24" +version = "3.0.26" description = "Library for building powerful interactive command lines in Python" category = "main" optional = false @@ -1737,7 +1793,7 @@ python-versions = "*" [[package]] name = "pyparsing" -version = "3.0.6" +version = "3.0.7" description = "Python parsing module" category = "main" optional = false @@ -1795,6 +1851,27 @@ requests = ">=2.25" [package.extras] validation = ["jsonschema (==3.2.0)"] +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.7.5" @@ -1843,7 +1920,7 @@ python-versions = "*" [[package]] name = "pywinpty" -version = "1.1.6" +version = "2.0.1" description = "Pseudo terminal support for Windows from Python." category = "main" optional = false @@ -1953,7 +2030,7 @@ idna2008 = ["idna"] [[package]] name = "rich" -version = "11.0.0" +version = "11.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = true @@ -2303,11 +2380,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "terminado" -version = "0.12.1" +version = "0.13.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] ptyprocess = {version = "*", markers = "os_name != \"nt\""} @@ -2330,12 +2407,20 @@ test = ["pytest", "pathlib2"] [[package]] name = "threadpoolctl" -version = "3.0.0" +version = "3.1.0" description = "threadpoolctl" category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "tomli" version = "1.2.3" @@ -2491,11 +2576,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "xarray" -version = "0.20.2" +version = "0.21.0" description = "N-D labeled arrays and datasets in Python" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" [package.dependencies] numpy = ">=1.18" @@ -2552,7 +2637,7 @@ viz = ["aiohttp", "cachetools", "distributed", "ipyleaflet", "jupyter-server-pro [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "74c2d251f03f2c8fc454877cb1f38dba959fa03670b8ebf1e9745a479d2cd79d" +content-hash = "280d124e3d767339e5a21bb1dc058607c13e9976577283a76ccd587e7c6f192c" [metadata.files] affine = [ @@ -2687,6 +2772,10 @@ async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, @@ -2832,8 +2921,8 @@ cffi = [ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -2890,8 +2979,8 @@ cycler = [ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] dask = [ - {file = "dask-2022.1.0-py3-none-any.whl", hash = "sha256:36e3765ef7800451deff87b3fe90cbc8a4963e65b3fd4e2a73c112c243f88608"}, - {file = "dask-2022.1.0.tar.gz", hash = "sha256:aee6eb6517f13dc00a3cc7ed9e13c728baa5850b8a35a2b93793d593a23ecbb8"}, + {file = "dask-2022.1.1-py3-none-any.whl", hash = "sha256:6caaf633c85ecd1197025de0b8bd244ef88b4b1810f315b09005b3fe5c21ee8e"}, + {file = "dask-2022.1.1.tar.gz", hash = "sha256:3d5e935792d8a5a61d19cb7e63771ee02cdfd6122e36beb15c2dad6257320c58"}, ] dask-labextension = [ {file = "dask_labextension-5.2.0-py3-none-any.whl", hash = "sha256:ee7b041cddf5753b95cbe2f0f0be6b8af5b86f5c1d5269ec03546d83da0c63ca"}, @@ -2929,8 +3018,8 @@ defusedxml = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] distributed = [ - {file = "distributed-2022.1.0-py3-none-any.whl", hash = "sha256:de1dd357cdaf1aa986898584593aa7d84574005f84dd3e30b6ae10c27f82c95d"}, - {file = "distributed-2022.1.0.tar.gz", hash = "sha256:71caba36b831ae4d4baacd44c702936198f236cae7d422edb50fe36ef5a68156"}, + {file = "distributed-2022.1.1-py3-none-any.whl", hash = "sha256:b8cdec07e46a7ddeaaa6cc1f3d8a50b0d102130b725e2246056ff531ea68a4c1"}, + {file = "distributed-2022.1.1.tar.gz", hash = "sha256:c227b5dd2c784830917679487f049fb943c154f1993f187d5c52076aa78a72d0"}, ] docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, @@ -2955,8 +3044,8 @@ flake8 = [ {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] fonttools = [ - {file = "fonttools-4.28.5-py3-none-any.whl", hash = "sha256:edf251d5d2cc0580d5f72de4621c338d8c66c5f61abb50cf486640f73c8194d5"}, - {file = "fonttools-4.28.5.zip", hash = "sha256:545c05d0f7903a863c2020e07b8f0a57517f2c40d940bded77076397872d14ca"}, + {file = "fonttools-4.29.0-py3-none-any.whl", hash = "sha256:ed9496e5650b977a697c50ac99c8e8331f9eae3f99e5ae649623359103306dfe"}, + {file = "fonttools-4.29.0.zip", hash = "sha256:f4834250db2c9855c3385459579dbb5cdf74349ab059ea0e619359b65ae72037"}, ] frozenlist = [ {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, @@ -3039,6 +3128,10 @@ heapdict = [ {file = "HeapDict-1.0.1-py3-none-any.whl", hash = "sha256:6065f90933ab1bb7e50db403b90cab653c853690c5992e69294c2de2b253fc92"}, {file = "HeapDict-1.0.1.tar.gz", hash = "sha256:8495f57b3e03d8e46d5f1b2cc62ca881aca392fd5cc048dc0aa2e1a6d23ecdb6"}, ] +hypothesis = [ + {file = "hypothesis-6.36.1-py3-none-any.whl", hash = "sha256:886342c291e3e592f26889f25b33aac78c6af62c736451f7cdad0a06ffa27325"}, + {file = "hypothesis-6.36.1.tar.gz", hash = "sha256:46cbee9d7aed822149af75ec63d5f86cd1042df69b2e8eae17b26a56a4dda781"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -3055,6 +3148,10 @@ importlib-resources = [ {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] ipykernel = [ {file = "ipykernel-6.7.0-py3-none-any.whl", hash = "sha256:6203ccd5510ff148e9433fd4a2707c5ce8d688f026427f46e13d7ebf9b3e9787"}, {file = "ipykernel-6.7.0.tar.gz", hash = "sha256:d82b904fdc2fd8c7b1fbe0fa481c68a11b4cd4c8ef07e6517da1f10cc3114d24"}, @@ -3100,8 +3197,8 @@ jsonschema = [ {file = "jsonschema-4.4.0.tar.gz", hash = "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83"}, ] jupyter-client = [ - {file = "jupyter_client-7.1.1-py3-none-any.whl", hash = "sha256:f0c576cce235c727e30b0a0da88c2755d0947d0070fa1bc45f195079ffd64e66"}, - {file = "jupyter_client-7.1.1.tar.gz", hash = "sha256:540ca35e57e83c5ece81abd9b781a57cba39a37c60a2a30c8c1b2f6663544343"}, + {file = "jupyter_client-7.1.2-py3-none-any.whl", hash = "sha256:d56f1c57bef42ff31e61b1185d3348a5b2bcde7c9a05523ae4dbe5ee0871797c"}, + {file = "jupyter_client-7.1.2.tar.gz", hash = "sha256:4ea61033726c8e579edb55626d8ee2e6bf0a83158ddf3751b8dd46b2c5cd1e96"}, ] jupyter-core = [ {file = "jupyter_core-4.9.1-py3-none-any.whl", hash = "sha256:1c091f3bbefd6f2a8782f2c1db662ca8478ac240e962ae2c66f0b87c818154ea"}, @@ -3112,12 +3209,12 @@ jupyter-resource-usage = [ {file = "jupyter_resource_usage-0.6.1-py3-none-any.whl", hash = "sha256:29e7eaaaff9044d53b8e7e0ebcc4b414d5f8449a3c2529ebf5f55d92ff7eaec2"}, ] jupyter-server = [ - {file = "jupyter_server-1.13.3-py3-none-any.whl", hash = "sha256:3608129b90cfdcfb7dd275f15a1113d119b7c19e8356303b14312ac5c216c42a"}, - {file = "jupyter_server-1.13.3.tar.gz", hash = "sha256:4d622161f4d378ff28548b49cc180024ce102d25ba5805821fcc17ab1bc5c754"}, + {file = "jupyter_server-1.13.4-py3-none-any.whl", hash = "sha256:3a1df2e27a322e84c028e52272e6ff72fd875f9a74c84409263c5c2f1afbf6fa"}, + {file = "jupyter_server-1.13.4.tar.gz", hash = "sha256:5fb5a219385338b1d13a013a68f54688b6a69ecff4e757fd230e27ecacdbf212"}, ] jupyter-server-proxy = [ - {file = "jupyter-server-proxy-3.2.0.tar.gz", hash = "sha256:816262f5711432dd79cbbd81e427cc2b592f522b0fae54368ed6e9d943384327"}, - {file = "jupyter_server_proxy-3.2.0-py3-none-any.whl", hash = "sha256:483333030546789af73823a808613f37980991fda162e0c9cc55065327c140fe"}, + {file = "jupyter-server-proxy-3.2.1.tar.gz", hash = "sha256:080e9910592d06422bdd93dfc1fa8350c6fdaec9fbbd050630e90f7a5593a4f7"}, + {file = "jupyter_server_proxy-3.2.1-py3-none-any.whl", hash = "sha256:150124483df109eed8280b13ea78c1a417dd8f2b513b624aa7f1201424b8cdf8"}, ] jupyter-sphinx = [ {file = "jupyter_sphinx-0.3.2-py3-none-any.whl", hash = "sha256:301e36d0fb3007bb5802f6b65b60c24990eb99c983332a2ab6eecff385207dc9"}, @@ -3334,78 +3431,65 @@ msgpack = [ {file = "msgpack-1.0.3.tar.gz", hash = "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e"}, ] multidict = [ - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, - {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, - {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, - {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, - {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, - {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, - {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, - {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, - {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, - {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, - {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, - {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, - {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, - {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -3420,8 +3504,8 @@ nbclient = [ {file = "nbclient-0.5.10.tar.gz", hash = "sha256:b5fdea88d6fa52ca38de6c2361401cfe7aaa7cd24c74effc5e489cec04d79088"}, ] nbconvert = [ - {file = "nbconvert-6.4.0-py3-none-any.whl", hash = "sha256:f5ec6a1fad9e3aa2bee7c6a1c4ad3e0fafaa7ff64f29ba56d9da7e1669f8521c"}, - {file = "nbconvert-6.4.0.tar.gz", hash = "sha256:5412ec774c6db4fccecb8c4ba07ec5d37d6dcf5762593cb3d6ecbbeb562ebbe5"}, + {file = "nbconvert-6.4.1-py3-none-any.whl", hash = "sha256:fe93bc42485c54c5a49a2324c834aca1ff315f320a535bed3e3c4e085d3eebe3"}, + {file = "nbconvert-6.4.1.tar.gz", hash = "sha256:7dce3f977c2f9651841a3c49b5b7314c742f24dd118b99e51b8eec13c504f555"}, ] nbformat = [ {file = "nbformat-5.1.3-py3-none-any.whl", hash = "sha256:eb8447edd7127d043361bc17f2f5a807626bc8e878c7709a1c647abda28a9171"}, @@ -3436,8 +3520,8 @@ nest-asyncio = [ {file = "nest_asyncio-1.5.4.tar.gz", hash = "sha256:f969f6013a16fadb4adcf09d11a68a4f617c6049d7af7ac2c676110169a63abd"}, ] notebook = [ - {file = "notebook-6.4.7-py3-none-any.whl", hash = "sha256:968e9c09639fe4b9dbf4b9f028daf861b563c124d735a99d6d48c09317553f31"}, - {file = "notebook-6.4.7.tar.gz", hash = "sha256:b01da66f11a203b3839d6afa4013674bcfff41c36552f9ad0fbcb2d93c92764a"}, + {file = "notebook-6.4.8-py3-none-any.whl", hash = "sha256:3e702fcc54b8ae597533c3864793b7a1e971dec9e112f67235828d8a798fd654"}, + {file = "notebook-6.4.8.tar.gz", hash = "sha256:1e985c9dc6f678bdfffb9dc657306b5469bfa62d73e03f74e8defbf76d284312"}, ] numpy = [ {file = "numpy-1.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d62d6b0870b53799204515145935608cdeb4cebb95a26800b6750e48884cc5b"}, @@ -3464,8 +3548,8 @@ numpy = [ {file = "numpy-1.22.1.zip", hash = "sha256:e348ccf5bc5235fc405ab19d53bec215bb373300e5523c7b476cc0da8a5e9973"}, ] numpydoc = [ - {file = "numpydoc-1.1.0-py3-none-any.whl", hash = "sha256:c53d6311190b9e3b9285bc979390ba0257ba9acde5eca1a7065fc8dfca9d46e8"}, - {file = "numpydoc-1.1.0.tar.gz", hash = "sha256:c36fd6cb7ffdc9b4e165a43f67bf6271a7b024d0bb6b00ac468c9e2bfc76448e"}, + {file = "numpydoc-1.2-py3-none-any.whl", hash = "sha256:3ecbb9feae080031714b63128912988ebdfd4c582a085d25b8d9f7ac23c2d9ef"}, + {file = "numpydoc-1.2.tar.gz", hash = "sha256:0cec233740c6b125913005d16e8a9996e060528afcb8b7cad3f2706629dfd6f7"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -3572,6 +3656,10 @@ platformdirs = [ {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] plumbum = [ {file = "plumbum-1.7.2-py2.py3-none-any.whl", hash = "sha256:0bbf431e31da988405de2fb36c3226f09c0c9cdf69c8480f8997f4b94b7370a1"}, {file = "plumbum-1.7.2.tar.gz", hash = "sha256:0d1bf908076bbd0484d16412479cb97d6843069ee19f99e267e11dd980040523"}, @@ -3581,12 +3669,12 @@ ply = [ {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, ] prometheus-client = [ - {file = "prometheus_client-0.12.0-py2.py3-none-any.whl", hash = "sha256:317453ebabff0a1b02df7f708efbab21e3489e7072b61cb6957230dd004a0af0"}, - {file = "prometheus_client-0.12.0.tar.gz", hash = "sha256:1b12ba48cee33b9b0b9de64a1047cbd3c5f2d0ab6ebcead7ddda613a750ec3c5"}, + {file = "prometheus_client-0.13.1-py3-none-any.whl", hash = "sha256:357a447fd2359b0a1d2e9b311a0c5778c330cfbe186d880ad5a6b39884652316"}, + {file = "prometheus_client-0.13.1.tar.gz", hash = "sha256:ada41b891b79fca5638bd5cfe149efa86512eaa55987893becd2c6d8d0a5dfc5"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.24-py3-none-any.whl", hash = "sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"}, - {file = "prompt_toolkit-3.0.24.tar.gz", hash = "sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6"}, + {file = "prompt_toolkit-3.0.26-py3-none-any.whl", hash = "sha256:4bcf119be2200c17ed0d518872ef922f1de336eb6d1ddbd1e089ceb6447d97c6"}, + {file = "prompt_toolkit-3.0.26.tar.gz", hash = "sha256:a51d41a6a45fd9def54365bca8f0402c8f182f2b6f7e29c74d55faeb9fb38ac4"}, ] psutil = [ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, @@ -3697,8 +3785,8 @@ pympler = [ {file = "Pympler-0.9.tar.gz", hash = "sha256:f2cbe7df622117af890249f2dea884eb702108a12d729d264b7c5983a6e06e47"}, ] pyparsing = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pyproj = [ {file = "pyproj-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2c41c9b7b5e1a1b0acc2b7b2f5de65b226f7b96c870888e4f679ff96322b1ed0"}, @@ -3753,6 +3841,10 @@ pystac-client = [ {file = "pystac-client-0.3.2.tar.gz", hash = "sha256:82561eeb143328494786ce108f5d290adfd461fd7e3b5f6ee68751f6f6b6ec0e"}, {file = "pystac_client-0.3.2-py3-none-any.whl", hash = "sha256:4bd944ba41306865357ce64c5cddf1a2870449c59e32d5fed52667779ccb44f2"}, ] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] python-dateutil = [ {file = "python-dateutil-2.7.5.tar.gz", hash = "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"}, {file = "python_dateutil-2.7.5-py2.py3-none-any.whl", hash = "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93"}, @@ -3784,12 +3876,11 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] pywinpty = [ - {file = "pywinpty-1.1.6-cp310-none-win_amd64.whl", hash = "sha256:5f526f21b569b5610a61e3b6126259c76da979399598e5154498582df3736ade"}, - {file = "pywinpty-1.1.6-cp36-none-win_amd64.whl", hash = "sha256:7576e14f42b31fa98b62d24ded79754d2ea4625570c016b38eb347ce158a30f2"}, - {file = "pywinpty-1.1.6-cp37-none-win_amd64.whl", hash = "sha256:979ffdb9bdbe23db3f46fc7285fd6dbb86b80c12325a50582b211b3894072354"}, - {file = "pywinpty-1.1.6-cp38-none-win_amd64.whl", hash = "sha256:2308b1fc77545427610a705799d4ead5e7f00874af3fb148a03e202437456a7e"}, - {file = "pywinpty-1.1.6-cp39-none-win_amd64.whl", hash = "sha256:c703bf569a98ab7844b9daf37e88ab86f31862754ef6910a8b3824993a525c72"}, - {file = "pywinpty-1.1.6.tar.gz", hash = "sha256:8808f07350c709119cc4464144d6e749637f98e15acc1e5d3c37db1953d2eebc"}, + {file = "pywinpty-2.0.1-cp310-none-win_amd64.whl", hash = "sha256:ec7d4841c82980519f31d2c61b7f93db4b773a66fce489a8a72377045fe04c4b"}, + {file = "pywinpty-2.0.1-cp37-none-win_amd64.whl", hash = "sha256:29550aafda86962b3b68e3454c11e26c1b8cf646dfafec33a4325c8d70ab4f36"}, + {file = "pywinpty-2.0.1-cp38-none-win_amd64.whl", hash = "sha256:dfdbcd0407c157c2024b0ea91b855caae25510fcf6c4da21c075253f05991a3a"}, + {file = "pywinpty-2.0.1-cp39-none-win_amd64.whl", hash = "sha256:c7cd0b30da5edd3e0b967842baa2aef1d205d991aa63a13c05afdb95d0812e69"}, + {file = "pywinpty-2.0.1.tar.gz", hash = "sha256:14e7321c6d43743af0de175fca9f111c5cc8d0a9f7c608c9e1cc69ec0d6ac146"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, @@ -3903,8 +3994,8 @@ rfc3986 = [ {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, ] rich = [ - {file = "rich-11.0.0-py3-none-any.whl", hash = "sha256:d7a8086aa1fa7e817e3bba544eee4fd82047ef59036313147759c11475f0dafd"}, - {file = "rich-11.0.0.tar.gz", hash = "sha256:c32a8340b21c75931f157466fefe81ae10b92c36a5ea34524dff3767238774a4"}, + {file = "rich-11.1.0-py3-none-any.whl", hash = "sha256:365ebcdbfb3aa8d4b0ed2490e0fbf7b886a39d14eb7ea5fb7aece950835e1eed"}, + {file = "rich-11.1.0.tar.gz", hash = "sha256:43e03d8eec12e21beaecc22c828a41c4247356414a12d5879834863d4ad53816"}, ] s3fs = [ {file = "s3fs-2022.1.0-py3-none-any.whl", hash = "sha256:3d20584130a6bf4679e4d9fa9a859597bab6553b8f4bd439168629c825d8ef01"}, @@ -4049,16 +4140,20 @@ tblib = [ {file = "tblib-1.7.0.tar.gz", hash = "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c"}, ] terminado = [ - {file = "terminado-0.12.1-py3-none-any.whl", hash = "sha256:09fdde344324a1c9c6e610ee4ca165c4bb7f5bbf982fceeeb38998a988ef8452"}, - {file = "terminado-0.12.1.tar.gz", hash = "sha256:b20fd93cc57c1678c799799d117874367cc07a3d2d55be95205b1a88fa08393f"}, + {file = "terminado-0.13.1-py3-none-any.whl", hash = "sha256:f446b522b50a7aa68b5def0a02893978fb48cb82298b0ebdae13003c6ee6f198"}, + {file = "terminado-0.13.1.tar.gz", hash = "sha256:5b82b5c6e991f0705a76f961f43262a7fb1e55b093c16dca83f16384a7f39b7b"}, ] testpath = [ {file = "testpath-0.5.0-py3-none-any.whl", hash = "sha256:8044f9a0bab6567fc644a3593164e872543bb44225b0e24846e2c89237937589"}, {file = "testpath-0.5.0.tar.gz", hash = "sha256:1acf7a0bcd3004ae8357409fc33751e16d37ccc650921da1094a86581ad1e417"}, ] threadpoolctl = [ - {file = "threadpoolctl-3.0.0-py3-none-any.whl", hash = "sha256:4fade5b3b48ae4b1c30f200b28f39180371104fccc642e039e0f2435ec8cc211"}, - {file = "threadpoolctl-3.0.0.tar.gz", hash = "sha256:d03115321233d0be715f0d3a5ad1d6c065fe425ddc2d671ca8e45e9fd5d7a52a"}, + {file = "threadpoolctl-3.1.0-py3-none-any.whl", hash = "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b"}, + {file = "threadpoolctl-3.1.0.tar.gz", hash = "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, @@ -4205,8 +4300,8 @@ wrapt = [ {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, ] xarray = [ - {file = "xarray-0.20.2-py3-none-any.whl", hash = "sha256:048eee6036efd2a03e7eb3e91b5359c38da9e80aa6fa82def644a102d59bd78f"}, - {file = "xarray-0.20.2.tar.gz", hash = "sha256:c2ebe80ca81b10a0241f6876dcc34ac9696e5c5cdcdf4758da7cf4bd732c41f7"}, + {file = "xarray-0.21.0-py3-none-any.whl", hash = "sha256:f7d45e7a504bc3231f8abf7977c296a4949ef1f9e5ca408b49fde54386ee9f8a"}, + {file = "xarray-0.21.0.tar.gz", hash = "sha256:2b3e7eb612ce571e1acbb397b31dacc47ba4e62e0c81710ef453c2e6842a5f49"}, ] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, diff --git a/pyproject.toml b/pyproject.toml index d88a820..fa337ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,8 +15,8 @@ Sphinx = {version = "^3.5.4", optional = true} aiohttp = {version = "^3.7.4", optional = true} cachetools = {version = "^4.2.2", optional = true} coiled = {version = "^0", optional = true} -dask = {extras = ["array"], version = ">= 2021.4.1, < 2023"} -distributed = {version = ">= 2021.4.1, < 2023", optional = true} +dask = {extras = ["array"], version = "^2022.1.1"} +distributed = {version = "^2022.1.1", optional = true} furo = {version = "^2021.4.11-beta.34", optional = true} geogif = {version = "^0", optional = true} ipyleaflet = {version = "^0.13.6", optional = true} @@ -57,6 +57,8 @@ pystac = "^1" snakeviz = "^2.1.0" sphinx-autobuild = "^2021.3.14" twine = "^3.4.1" +pytest = "^6.2.5" +hypothesis = "^6.35.0" [tool.poetry.extras] binder = [ diff --git a/stackstac/nodata_reader.py b/stackstac/nodata_reader.py index e453797..8aab7f1 100644 --- a/stackstac/nodata_reader.py +++ b/stackstac/nodata_reader.py @@ -1,4 +1,4 @@ -from typing import Tuple, Type, Union, cast +from typing import Tuple, Type, Union import re import numpy as np @@ -37,11 +37,7 @@ def __setstate__(self, state: State) -> None: def nodata_for_window(window: Window, fill_value: Union[int, float], dtype: np.dtype): - height = cast(int, window.height) - width = cast(int, window.width) - # Argument of type "tuple[_T@attrib, _T@attrib]" cannot be assigned to parameter "shape" of type "_ShapeLike" - # in function "full" - return np.full((height, width), fill_value, dtype) + return np.full((window.height, window.width), fill_value, dtype) def exception_matches(e: Exception, patterns: Tuple[Exception, ...]) -> bool: diff --git a/stackstac/reader_protocol.py b/stackstac/reader_protocol.py index ad4f457..81f7da4 100644 --- a/stackstac/reader_protocol.py +++ b/stackstac/reader_protocol.py @@ -26,9 +26,6 @@ class Reader(Pickleable, Protocol): Protocol for a thread-safe, lazily-loaded object for reading data from a single-band STAC asset. """ - fill_value: Union[int, float] - dtype: np.dtype - def __init__( self, *, @@ -116,12 +113,8 @@ class FakeReader: or inherent to the dask graph. """ - def __init__( - self, *, dtype: np.dtype, fill_value: Union[int, float], **kwargs - ) -> None: - pass + def __init__(self, *, dtype: np.dtype, **kwargs) -> None: self.dtype = dtype - self.fill_value = fill_value def read(self, window: Window, **kwargs) -> np.ndarray: return np.random.random((window.height, window.width)).astype(self.dtype) diff --git a/stackstac/stack.py b/stackstac/stack.py index 7494990..6ab51e0 100644 --- a/stackstac/stack.py +++ b/stackstac/stack.py @@ -14,7 +14,7 @@ from .rio_env import LayeredEnv from .rio_reader import AutoParallelRioReader from .stac_types import ItemCollectionIsh, ItemIsh, items_to_plain -from .to_dask import items_to_dask +from .to_dask import items_to_dask, ChunksParam def stack( @@ -28,7 +28,7 @@ def stack( bounds_latlon: Optional[Bbox] = None, snap_bounds: bool = True, resampling: Resampling = Resampling.nearest, - chunksize: int = 1024, + chunksize: ChunksParam = 1024, dtype: np.dtype = np.dtype("float64"), fill_value: Union[int, float] = np.nan, rescale: bool = True, @@ -161,35 +161,27 @@ def stack( The rasterio resampling method to use when reprojecting or rescaling data to a different CRS or resolution. Default: ``rasterio.enums.Resampling.nearest``. chunksize: - The chunksize to use for the spatial component of the Dask array, in pixels. - Default: 1024. Can be given in any format Dask understands for a ``chunks=`` argument, - such as ``1024``, ``(1024, 2048)``, ``15 MB``, etc. - - This is basically the "tile size" all your operations will be parallelized over. - Generally, you should use the internal tile size/block size of whatever data - you're accessing (or a multiple of that). For example, if all the assets are in - Cloud-Optimized GeoTIFF files with an internal tilesize of 512, pass ``chunksize=512``. - - You want the chunks of the Dask array to align with the internal tiles of the data. - Otherwise, if those grids don't line up, processing one chunk of your Dask array could - require reading many tiles from the data, parts of which will then be thrown out. - Additionally, those same data tiles might need to be re-read (and re-thrown-out) - for a neighboring Dask chunk, which is just as inefficient as it sounds (though setting - a high ``GDAL_CACHEMAX`` via ``gdal_env`` will help keep more data tiles cached, - at the expense of using more memory). - - Of course, when reprojecting data to a new grid, the internal tilings of each input - almost certainly won't line up anyway, so some misalignment is inevitable, and not - that big of a deal. - - **This is the one parameter we can't pick for you automatically**, because the STAC - specification offers no metadata about the internal tiling of the assets. We'd - have to open the data files to find out, which is very slow. But to make an educated - guess, you should look at ``rasterio.open(url).block_shapes`` for a few sample assets. - - The most important thing to avoid is making your ``chunksize`` here *smaller* than - the internal tilesize of the data. If you want small Dask chunks for other reasons, - don't set it here---instead, call ``.chunk`` on the DataArray to re-chunk it. + The chunksize to use for the Dask array. Default: 1024. Picking a good chunksize will + have significant effects on performance! + + Can be given in any format :ref:`Dask understands `, + such as ``1024``, ``(1024, 2048)``, ``(10, "auto", 512, 512)``, ``15 MB``, etc. + + If only 1 or 2 sizes are given, like ``2048`` or ``(512, 1024)``, this is used to chunk + just the spatial dimensions (last two). The time and band dimensions will have a chunksize of 1, + meaning that every STAC Asset will be its own chunk. (This is the default.) + + If you'll be filtering items somewhat randomly (like ``stack[stack["eo:cloud_cover"] < 20]``), + you want the chunksize to be like ``(1, X, X, X)``. Otherwise, if you had a chunksize like + ``(3, 1, X, X)``, Dask would always load three items per chunk, even if two of them would be + immediately thrown away because they didn't match the cloud-cover filter. + + However, when your graph starts getting too large for Dask to handle, using a larger chunksize + for the time or band dimensions can help a lot. For example, ``chunksize=(10, 1, 512, 512)`` would + process in 512x512 pixel tiles, loading 10 assets at a time per tile. ``chunksize=(-1, 1, 512, 512)`` + would load *every* item within the 512x512 tile into the chunk. + If you'll be immediately compositing the data (like ``.median("time")``), doing this is + often a good idea because you'll be flattening the assets together anyway. dtype: The NumPy data type of the output array. Default: ``float64``. Must be a data type that's compatible with ``fill_value``. diff --git a/stackstac/testing/__init__.py b/stackstac/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stackstac/testing/strategies.py b/stackstac/testing/strategies.py new file mode 100644 index 0000000..edb5603 --- /dev/null +++ b/stackstac/testing/strategies.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from hypothesis import strategies as st +import hypothesis.extra.numpy as st_np + +from stackstac.to_dask import ChunksParam + + +@st.composite +def simple_bboxes( + draw: st.DrawFn, + minx: int = -100, + miny: int = -100, + maxx: int = 100, + maxy: int = 100, + *, + zero_size: bool = True, +) -> tuple[int, int, int, int]: + west = draw(st.integers(minx, maxx - 1)) + south = draw(st.integers(miny, maxy - 1)) + east = draw(st.integers(west if zero_size else west + 1, maxy)) + north = draw(st.integers(south if zero_size else south + 1, maxy)) + return (west, south, east, north) + + +raster_dtypes = ( + st_np.unsigned_integer_dtypes() + | st_np.integer_dtypes() + | st_np.floating_dtypes() + | st_np.complex_number_dtypes() +) + + +def chunksizes( + ndim: int, + *, + max_side: int | None = None, + ints: bool = True, + auto: bool = True, + bytes: bool = True, + none: bool = True, + minus_one: bool = True, + tuples: bool = True, + dicts: bool = True, + singleton: bool = True, +) -> st.SearchStrategy[ChunksParam]: + "Generates arguments for ``chunks=`` for Dask." + + sizes = st.shared( + st.sampled_from(["8B", f"{max_side*8}B" if max_side else "100KiB"]), key="size" + ) + chunk_val_strategies = [] + if ints: + chunk_val_strategies.append(st.integers(1, max_side)) + if auto: + chunk_val_strategies.append(st.just("auto")) + if bytes: + chunk_val_strategies.append(sizes) + + toplevel_chunk_vals = st.one_of(chunk_val_strategies) + + if none: + chunk_val_strategies.append(st.none()) + if minus_one: + chunk_val_strategies.append(st.just(-1)) + + chunk_vals = st.one_of(chunk_val_strategies) + + final = [] + if singleton: + final.append(toplevel_chunk_vals) + if tuples: + final.append(st.tuples(*(chunk_vals,) * ndim)) + if dicts: + final.append( + st.dictionaries(st.integers(1, ndim), chunk_vals, min_size=1, max_size=ndim) + ) + + return st.one_of(final) diff --git a/stackstac/tests/__init__.py b/stackstac/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stackstac/tests/test_to_dask.py b/stackstac/tests/test_to_dask.py new file mode 100644 index 0000000..1a85bfb --- /dev/null +++ b/stackstac/tests/test_to_dask.py @@ -0,0 +1,202 @@ +from __future__ import annotations +from threading import Lock +from typing import ClassVar + +from hypothesis import given, settings, strategies as st +import hypothesis.extra.numpy as st_np +import numpy as np +from rasterio import windows +import dask.core +import dask.threaded +from dask.array.utils import assert_eq + +from stackstac.raster_spec import Bbox, RasterSpec +from stackstac.prepare import ASSET_TABLE_DT +from stackstac.to_dask import ( + ChunksParam, + items_to_dask, + normalize_chunks, + window_from_bounds, +) +from stackstac.testing import strategies as st_stc + + +@st.composite +def asset_tables( + draw: st.DrawFn, + max_side: int | None = None, +) -> np.ndarray: + """ + Generate asset tables where entries have random bounds, and are randomly missing. + Each URL is of the form ``"fake://{i}/{j}"``, so you can parse it within a Reader + to know the (time, band) coordinates of that asset. Bounds may be zero-size (the min + and max are equal). + + An example of an asset table: + + np.array( + [ + # Encode the (i, j) index in the table in the URL + [("fake://0/0", [0, 0, 2, 2]), ("fake://0/1", [0, 0, 2, 2])], + [("fake://1/0", [0, 3, 2, 5]), ("fake://1/1", [10, 13, 12, 15])], + [("fake://2/0", [1, 3, 2, 6]), ("fake://2/1", [1, 3, 2, 7])], + [(None, None), (None, None)], + ], + dtype=ASSET_TABLE_DT, + ) + """ + shape = draw( + st_np.array_shapes(min_dims=2, max_dims=2, max_side=max_side), label="shape" + ) + bounds_arr = draw( + st_np.arrays( + object, + shape, + elements=st_stc.simple_bboxes(), + fill=st.none(), + ), + label="bounds_arr", + ) + + asset_table = np.empty_like(bounds_arr, ASSET_TABLE_DT) + for (i, j), bounds in np.ndenumerate(bounds_arr): + if bounds: + # Encode the (i, j) index in the table in the URL + asset_table[i, j] = (f"fake://{i}/{j}", bounds) + + return asset_table + + +@given( + st.data(), + asset_tables(max_side=5), + st_stc.simple_bboxes(-4, -4, 4, 4, zero_size=False), + st_stc.raster_dtypes, + st_stc.chunksizes( + 4, + max_side=10, + auto=False, + bytes=False, + none=False, + minus_one=False, + dicts=False, + singleton=False, + ), +) +@settings(max_examples=500, print_blob=True) +def test_items_to_dask( + data: st.DataObject, + asset_table: np.ndarray, + bounds: Bbox, + dtype_: np.dtype, + chunksize: tuple[int, int, int, int], +): + spec_ = RasterSpec(4326, bounds, (0.5, 0.5)) + fill_value_ = data.draw(st_np.from_dtype(dtype_), label="fill_value") + + # Build expected array of the final stack. + # Start with all nodata, then write data in for each asset. + # The `TestReader` will then just read from this final array, sliced to the appropriate window. + # (This is much easier than calculating where to put nodata values ourselves.) + + asset_windows: dict[str, windows.Window] = {} + results = np.full(asset_table.shape + spec_.shape, fill_value_, dtype_) + for i, item in enumerate(asset_table): + for j, asset in enumerate(item): + url = asset["url"] + if url is None: + continue + assert url == f"fake://{i}/{j}" + + window = window_from_bounds(asset["bounds"], spec_.transform) + asset_windows[url] = window + + chunk = results[(i, j) + windows.window_index(window)] + if chunk.size: + # Asset falls within final bounds + chunk[:] = np.random.default_rng().uniform(0, 128, chunk.shape) + + class TestReader: + opened: ClassVar[set[str]] = set() + lock: ClassVar[Lock] = Lock() + + def __init__( + self, + *, + url: str, + spec: RasterSpec, + dtype: np.dtype, + fill_value: int | float, + **kwargs, + ) -> None: + with self.lock: + # Each URL should only be opened once. + # The `dask.annotate` on the `asset_table_to_reader_and_window` step is necessary for this, + # otherwise blockwise fusion would merge the Reader creation into every `fetch_raster_window`! + assert url not in self.opened + self.opened.add(url) + i, j = map(int, url[7:].split("/")) + self.full_data = results[i, j] + self.window = asset_windows[url] + + assert spec == spec_ + assert dtype == dtype_ + np.testing.assert_equal(fill_value, fill_value_) + + def read(self, window: windows.Window) -> np.ndarray: + assert 0 < window.height <= chunksize[2] + assert 0 < window.width <= chunksize[3] + # Read should be bypassed entirely if windows don't intersect + assert windows.intersect(window, self.window) + return self.full_data[window.toslices()] + + def close(self) -> None: + pass + + def __getstate__(self) -> dict: + return self.__dict__ + + def __setstate__(self, state): + self.__init__(**state) + + arr = items_to_dask( + asset_table, + spec_, + chunksize, + dtype=dtype_, + fill_value=fill_value_, + reader=TestReader, + ) + assert arr.chunksize == tuple( + min(x, y) for x, y in zip(asset_table.shape + spec_.shape, chunksize) + ) + assert arr.dtype == dtype_ + + assert_eq(arr, results, equal_nan=True) + + # Check that entirely-empty chunks are broadcast-tricked into being tiny. + # NOTE: unfortunately, this computes the array again, which slows down tests. + # But passing a computed array into `assert_eq` would skip handy checks for chunks, meta, etc. + TestReader.opened.clear() + chunks = dask.threaded.get(arr.dask, list(dask.core.flatten(arr.__dask_keys__()))) + for chunk in chunks: + if ( + np.isnan(chunk) if np.isnan(fill_value_) else np.equal(chunk, fill_value_) + ).all(): + assert chunk.strides == (0, 0, 0, 0) + + +@given( + st_stc.chunksizes(4, max_side=1000), + st_np.array_shapes(min_dims=4, max_dims=4), + st_stc.raster_dtypes, +) +def test_normalize_chunks( + chunksize: ChunksParam, shape: tuple[int, int, int, int], dtype: np.dtype +): + chunks = normalize_chunks(chunksize, shape, dtype) + numblocks = tuple(map(len, chunks)) + assert len(numblocks) == 4 + assert all(x >= 1 for t in chunks for x in t) + if isinstance(chunksize, int) or isinstance(chunks, tuple) and len(chunks) == 2: + assert numblocks[:2] == shape[:2] diff --git a/stackstac/to_dask.py b/stackstac/to_dask.py index 58a788c..513a1c0 100644 --- a/stackstac/to_dask.py +++ b/stackstac/to_dask.py @@ -1,11 +1,14 @@ from __future__ import annotations -import itertools -from typing import Optional, Tuple, Type, TypeVar, Union +from typing import Dict, Literal, Optional, Tuple, Type, Union import warnings +from affine import Affine import dask import dask.array as da +from dask.blockwise import blockwise +from dask.highlevelgraph import HighLevelGraph +from dask.layers import ArraySliceDep import numpy as np from rasterio import windows from rasterio.enums import Resampling @@ -14,14 +17,14 @@ from .rio_reader import AutoParallelRioReader, LayeredEnv from .reader_protocol import Reader -# from xarray.backends import CachingFileManager -# from xarray.backends.locks import DummyLock +ChunkVal = Union[int, Literal["auto"], str, None] +ChunksParam = Union[ChunkVal, Tuple[ChunkVal, ...], Dict[int, ChunkVal]] def items_to_dask( asset_table: np.ndarray, spec: RasterSpec, - chunksize: int, + chunksize: ChunksParam, resampling: Resampling = Resampling.nearest, dtype: np.dtype = np.dtype("float64"), fill_value: Union[int, float] = np.nan, @@ -30,81 +33,83 @@ def items_to_dask( gdal_env: Optional[LayeredEnv] = None, errors_as_nodata: Tuple[Exception, ...] = (), ) -> da.Array: + "Create a dask Array from an asset table" errors_as_nodata = errors_as_nodata or () # be sure it's not None - if fill_value is not None and not np.can_cast(fill_value, dtype): + if not np.can_cast(fill_value, dtype): raise ValueError( f"The fill_value {fill_value} is incompatible with the output dtype {dtype}. " f"Either use `dtype={np.array(fill_value).dtype.name!r}`, or pick a different `fill_value`." ) + chunks = normalize_chunks(chunksize, asset_table.shape + spec.shape, dtype) + chunks_tb, chunks_yx = chunks[:2], chunks[2:] + # The overall strategy in this function is to materialize the outer two dimensions (items, assets) - # as one dask array, then the chunks of the inner two dimensions (y, x) as another dask array, then use - # Blockwise to represent the cartesian product between them, to avoid materializing that entire graph. + # as one dask array (the "asset table"), then map a function over it which opens each URL as a `Reader` + # instance (the "reader table"). + # Then, we use the `ArraySliceDep` `BlockwiseDep` to represent the inner inner two dimensions (y, x), and + # `Blockwise` to create the cartesian product between them, avoiding materializing that entire graph. # Materializing the (items, assets) dimensions is unavoidable: every asset has a distinct URL, so that information # has to be included somehow. - # make URLs into dask array with 1-element chunks (one chunk per asset) + # make URLs into dask array, chunked as requested for the time,band dimensions asset_table_dask = da.from_array( asset_table, - chunks=1, + chunks=chunks_tb, inline_array=True, name="asset-table-" + dask.base.tokenize(asset_table), ) - # then map a function over each chunk that opens that URL as a rasterio dataset - # HACK: `url_to_ds` doesn't even return a NumPy array, so `datasets.compute()` would fail - # (because the chunks can't be `np.concatenate`d together). - # but we're just using this for the graph. - # So now we have an array of shape (items, assets), chunksize 1---the outer two dimensions of our final array. - datasets = asset_table_dask.map_blocks( - asset_entry_to_reader_and_window, - spec, - resampling, - dtype, - fill_value, - rescale, - gdal_env, - errors_as_nodata, - reader, - meta=asset_table_dask._meta, - ) - - # MEGAHACK: generate a fake array for our spatial dimensions following `shape` and `chunksize`, - # but where each chunk is not actually NumPy a array, but just a 2-tuple of the (y-slice, x-slice) - # to `read` from the rasterio dataset to fetch that window of data. - shape = spec.shape - name = "slices-" + dask.base.tokenize(chunksize, shape) - chunks = da.core.normalize_chunks(chunksize, shape) - keys = itertools.product([name], *(range(len(bds)) for bds in chunks)) - slices = da.core.slices_from_chunks(chunks) - # HACK: `slices_fake_arr` is in no way a real dask array: the chunks aren't ndarrays; they're tuples of slices! - # We just stick it in an Array container to make dask's blockwise logic handle broadcasting between the graphs of - # `datasets` and `slices_fake_arr`. - slices_fake_arr = da.Array( - dict(zip(keys, slices)), name, chunks, meta=datasets._meta - ) + # map a function over each chunk that opens that URL as a rasterio dataset + with dask.annotate(fuse=False): + # ^ HACK: prevent this layer from fusing to the next `fetch_raster_window` one. + # This uses the fact that blockwise fusion doesn't happen when the layers' annotations + # don't match, which may not be behavior we can rely on. + # (The actual content of the annotation is irrelevant here, just that there is one.) + reader_table = asset_table_dask.map_blocks( + asset_table_to_reader_and_window, + spec, + resampling, + dtype, + fill_value, + rescale, + gdal_env, + errors_as_nodata, + reader, + dtype=object, + ) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=da.core.PerformanceWarning) - rasters = da.blockwise( + name = f"fetch_raster_window-{dask.base.tokenize(reader_table, chunks, dtype, fill_value)}" + # TODO use `da.blockwise` once it supports `BlockwiseDep`s as arguments + lyr = blockwise( fetch_raster_window, + name, "tbyx", - datasets, + reader_table.name, "tb", - slices_fake_arr, + ArraySliceDep(chunks_yx), "yx", - meta=np.ndarray((), dtype=dtype), # TODO dtype + dtype, + None, + fill_value, + None, + numblocks={reader_table.name: reader_table.numblocks}, # ugh ) + dsk = HighLevelGraph.from_collections(name, lyr, [reader_table]) + rasters = da.Array(dsk, name, chunks, meta=np.ndarray((), dtype=dtype)) + return rasters -ReaderT = TypeVar("ReaderT", bound=Reader) +ReaderTableEntry = Optional[Tuple[Reader, windows.Window]] -def asset_entry_to_reader_and_window( - asset_entry: np.ndarray, +def asset_table_to_reader_and_window( + asset_table: np.ndarray, spec: RasterSpec, resampling: Resampling, dtype: np.dtype, @@ -112,66 +117,140 @@ def asset_entry_to_reader_and_window( rescale: bool, gdal_env: Optional[LayeredEnv], errors_as_nodata: Tuple[Exception, ...], - reader: Type[ReaderT], -) -> Tuple[ReaderT, windows.Window] | np.ndarray: - asset_entry = asset_entry[0, 0] - # ^ because dask adds extra outer dims in `from_array` - url = asset_entry["url"] - if url is None: - # Signifies empty value - return np.array(fill_value, dtype) - - asset_bounds: Bbox = asset_entry["bounds"] - asset_window = windows.from_bounds(*asset_bounds, transform=spec.transform) - - return ( - # CachingFileManager( - # AutoParallelRioBackend, # TODO other backends - # url, - # spec, - # resampling, - # gdal_env, - # lock=DummyLock(), - # # ^ NOTE: this lock only protects the file cache, not the file itself. - # # xarray's file cache is already thread-safe, so using a lock is pointless. - # ), - # NOTE: skip the `CachingFileManager` for now to be sure datasets aren't leaked. - # There are a few issues with `CachingFileManager`: faulty ref-counting logic, - # and misleading `close` and `lock` interfaces. Either refactor that upstream, - # or implement our own caching logic. - reader( - url=url, - spec=spec, - resampling=resampling, - dtype=dtype, - fill_value=fill_value, - rescale=rescale, - gdal_env=gdal_env, - errors_as_nodata=errors_as_nodata, - ), - asset_window, - ) + reader: Type[Reader], +) -> np.ndarray: + """ + "Open" an asset table by creating a `Reader` for each asset. + + This function converts the asset table (or chunks thereof) into an object array, + where each element contains a tuple of the `Reader` and `Window` for that asset, + or None if the element has no URL. + """ + reader_table = np.empty_like(asset_table, dtype=object) + for index, asset_entry in np.ndenumerate(asset_table): + url: str | None = asset_entry["url"] + if url: + asset_bounds: Bbox = asset_entry["bounds"] + asset_window = window_from_bounds(asset_bounds, spec.transform) + + entry: ReaderTableEntry = ( + reader( + url=url, + spec=spec, + resampling=resampling, + dtype=dtype, + fill_value=fill_value, + rescale=rescale, + gdal_env=gdal_env, + errors_as_nodata=errors_as_nodata, + ), + asset_window, + ) + reader_table[index] = entry + return reader_table def fetch_raster_window( - asset_entry: Tuple[ReaderT, windows.Window] | np.ndarray, - slices: Tuple[slice, ...], + reader_table: np.ndarray, + slices: Tuple[slice, slice], + dtype: np.dtype, + fill_value: Union[int, float], ) -> np.ndarray: + "Do a spatially-windowed read of raster data from all the Readers in the table." + assert len(slices) == 2, slices current_window = windows.Window.from_slices(*slices) - if isinstance(asset_entry, tuple): - reader, asset_window = asset_entry - # check that the window we're fetching overlaps with the asset - if windows.intersect(current_window, asset_window): - # backend: Backend = manager.acquire(needs_lock=False) - data = reader.read(current_window) + assert reader_table.size, f"Empty reader_table: {reader_table.shape=}" + # Start with an empty output array, using the broadcast trick for even fewer memz. + # If none of the assets end up actually existing, or overlapping the current window, + # or containing data, we'll just return this 1-element array that's been broadcast + # to look like a full-size array. + output = np.broadcast_to( + np.array(fill_value, dtype), + reader_table.shape + (current_window.height, current_window.width), + ) + + all_empty: bool = True + entry: ReaderTableEntry + for index, entry in np.ndenumerate(reader_table): + if entry: + reader, asset_window = entry + # Only read if the window we're fetching actually overlaps with the asset + if windows.intersect(current_window, asset_window): + # NOTE: when there are multiple assets, we _could_ parallelize these reads with our own threadpool. + # However, that would probably increase memory usage, since the internal, thread-local GDAL datasets + # would end up copied to even more threads. + + # TODO when the Reader won't be rescaling, support passing `output` to avoid the copy? + data = reader.read(current_window) + + if all_empty: + # Turn `output` from a broadcast-trick array to a real array, so it's writeable + if ( + np.isnan(data) + if np.isnan(fill_value) + else np.equal(data, fill_value) + ).all(): + # Unless the data we just read is all empty anyway + continue + output = np.array(output) + all_empty = False + + output[index] = data + + return output + - return data[None, None] - fill_arr = np.array(reader.fill_value, reader.dtype) - else: - fill_arr: np.ndarray = asset_entry +def normalize_chunks( + chunks: ChunksParam, shape: Tuple[int, int, int, int], dtype: np.dtype +) -> Tuple[Tuple[int, ...], Tuple[int, ...], Tuple[int, ...], Tuple[int, ...]]: + """ + Normalize chunks to tuple of tuples, assuming 1D and 2D chunks only apply to spatial coordinates - # no dataset, or we didn't overlap it: return empty data. - # use the broadcast trick for even fewer memz - return np.broadcast_to(fill_arr, (1, 1) + windows.shape(current_window)) + If only 1 or 2 chunks are given, assume they're for the ``y, x`` coordinates, + and that the ``time, band`` coordinates should be chunksize 1. + """ + # TODO implement our own auto-chunking that makes the time,band coordinates + # >1 if the spatial chunking would create too many tasks? + if isinstance(chunks, int): + chunks = (1, 1, chunks, chunks) + elif isinstance(chunks, tuple) and len(chunks) == 2: + chunks = (1, 1) + chunks + return da.core.normalize_chunks( + chunks, + shape, + dtype=dtype, + previous_chunks=((1,) * shape[0], (1,) * shape[1], (shape[2],), (shape[3],)), + # ^ Give dask some hint of the physical layout of the data, so it prefers widening + # the spatial chunks over bundling together items/assets. This isn't totally accurate. + ) + + +# FIXME remove this once rasterio bugs are fixed +def window_from_bounds(bounds: Bbox, transform: Affine) -> windows.Window: + "Get the window corresponding to the bounding coordinates (correcting for rasterio bugs)" + window = windows.from_bounds( + *bounds, + transform=transform, + precision=0.0 + # ^ https://github.com/rasterio/rasterio/issues/2374 + ) + + # Trim negative `row_off`/`col_off` to work around https://github.com/rasterio/rasterio/issues/2378 + # Note this does actually alter the window: it clips off anything that was out-of-bounds to the + # west/north of the `transform`'s origin. So the size and origin of the window is no longer accurate. + # This is okay for our purposes, since we only use these windows for intersection-testing to see if + # an asset intersects our current chunk. We don't care about the parts of the asset that fall + # outside our AOI. + window = windows.Window( + max(window.col_off, 0), # type: ignore "Expected 0 positional arguments" + max(window.row_off, 0), + (max(window.col_off + window.width, 0) if window.col_off < 0 else window.width), + ( + max(window.row_off + window.height, 0) + if window.row_off < 0 + else window.height + ), + ) + return window