diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..27be97f0f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = replay/ann/index_stores/hdfs_index_store.py \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7babe3f17..4550ec652 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,4 +46,4 @@ jobs: - name: pytest run: | . ./venv/bin/activate - pytest --cov=replay --cov-report=term-missing --doctest-modules replay --cov-fail-under=93 tests + pytest --cov-config=.coveragerc --cov=replay --cov-report=term-missing --doctest-modules replay --cov-fail-under=93 tests diff --git a/poetry.lock b/poetry.lock index 1ecb3fb3c..da8021740 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -14,14 +14,14 @@ files = [ [[package]] name = "alembic" -version = "1.9.3" +version = "1.10.4" description = "A database migration tool for SQLAlchemy." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "alembic-1.9.3-py3-none-any.whl", hash = "sha256:ed2f73ea9c986f43af8ad7502c5f60d6bb1400bcd6d29f230e760e08884cb476"}, - {file = "alembic-1.9.3.tar.gz", hash = "sha256:8fd6aaea56f5a703a190d25a705dfa91d7c313bb71de2f9c68f5abdcaf5df164"}, + {file = "alembic-1.10.4-py3-none-any.whl", hash = "sha256:43942c3d4bf2620c466b91c0f4fca136fe51ae972394a0cc8b90810d664e4f5c"}, + {file = "alembic-1.10.4.tar.gz", hash = "sha256:295b54bbb92c4008ab6a7dcd1e227e668416d6f84b98b3c4446a2bc6214a556b"}, ] [package.dependencies] @@ -29,6 +29,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} importlib-resources = {version = "*", markers = "python_version < \"3.9\""} Mako = "*" SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" [package.extras] tz = ["python-dateutil"] @@ -170,22 +171,25 @@ files = [ [[package]] name = "attrs" -version = "22.2.0" +version = "23.1.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + [package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "autowoe" @@ -218,18 +222,18 @@ tqdm = ">=4.62.3,<5.0.0" [[package]] name = "babel" -version = "2.11.0" +version = "2.12.1" description = "Internationalization utilities" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, ] [package.dependencies] -pytz = ">=2015.7" +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "backcall" @@ -245,14 +249,14 @@ files = [ [[package]] name = "beautifulsoup4" -version = "4.11.2" +version = "4.12.2" description = "Screen-scraping library" category = "dev" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.11.2-py3-none-any.whl", hash = "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39"}, - {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, ] [package.dependencies] @@ -264,37 +268,37 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.1.0" +version = "23.3.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, - {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, - {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, - {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, - {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, - {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, - {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, - {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, - {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, - {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, - {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, - {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, - {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, - {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, ] [package.dependencies] @@ -332,50 +336,71 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + [[package]] name = "catboost" -version = "1.1.1" -description = "Catboost Python Package" +version = "1.2" +description = "CatBoost Python Package" category = "main" optional = false python-versions = "*" files = [ - {file = "catboost-1.1.1-cp310-none-macosx_10_6_universal2.whl", hash = "sha256:93532f6807228f74db9c8184a0893ab222232d23fc5b3db534e2d8fedbba42cf"}, - {file = "catboost-1.1.1-cp310-none-manylinux1_x86_64.whl", hash = "sha256:7c7364d79d5ff9deb56956560ba91a1b62b84204961d540bffd97f7b995e8cba"}, - {file = "catboost-1.1.1-cp310-none-win_amd64.whl", hash = "sha256:5ec0c9bd65e53ae6c26d17c06f9c28e4febbd7cbdeb858460eb3d34249a10f30"}, - {file = "catboost-1.1.1-cp36-none-macosx_10_6_universal2.whl", hash = "sha256:60acc4448eb45242f4d30aea6ccdf45bfaa8646bbc4ede3200cf25ba0d6bcf3d"}, - {file = "catboost-1.1.1-cp36-none-manylinux1_x86_64.whl", hash = "sha256:b7443b40b5ddb141c6d14bff16c13f7cf4852893b57d7eda5dff30fb7517e14d"}, - {file = "catboost-1.1.1-cp36-none-win_amd64.whl", hash = "sha256:190828590270e3dea5fb58f0fd13715ee2324f6ee321866592c422a1da141961"}, - {file = "catboost-1.1.1-cp37-none-macosx_10_6_universal2.whl", hash = "sha256:a2fe4d08a360c3c3cabfa3a94c586f2261b93a3fff043ae2b43d2d4de121c2ce"}, - {file = "catboost-1.1.1-cp37-none-manylinux1_x86_64.whl", hash = "sha256:4e350c40920dbd9644f1c7b88cb74cb8b96f1ecbbd7c12f6223964465d83b968"}, - {file = "catboost-1.1.1-cp37-none-win_amd64.whl", hash = "sha256:0033569f2e6314a04a84ec83eecd39f77402426b52571b78991e629d7252c6f7"}, - {file = "catboost-1.1.1-cp38-none-macosx_10_6_universal2.whl", hash = "sha256:454aae50922b10172b94971033d4b0607128a2e2ca8a5845cf8879ea28d80942"}, - {file = "catboost-1.1.1-cp38-none-manylinux1_x86_64.whl", hash = "sha256:3fd12d9f1f89440292c63b242ccabdab012d313250e2b1e8a779d6618c734b32"}, - {file = "catboost-1.1.1-cp38-none-win_amd64.whl", hash = "sha256:840348bf56dd11f6096030208601cbce87f1e6426ef33140fb6cc97bceb5fef3"}, - {file = "catboost-1.1.1-cp39-none-macosx_10_6_universal2.whl", hash = "sha256:9e7c47050c8840ccaff4d394907d443bda01280a30778ae9d71939a7528f5ae3"}, - {file = "catboost-1.1.1-cp39-none-manylinux1_x86_64.whl", hash = "sha256:a60ae2630f7b3752f262515a51b265521a4993df75dea26fa60777ec6e479395"}, - {file = "catboost-1.1.1-cp39-none-win_amd64.whl", hash = "sha256:156264dbe9e841cb0b6333383e928cb8f65df4d00429a9771eb8b06b9bcfa17c"}, + {file = "catboost-1.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:b7ff06dd4e72ba5ba2d827d0870080485dcd91dfb30b820e377f6a634258c47a"}, + {file = "catboost-1.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:93a9ef21b3b1817e6480854f40f0a2daad8393ee92b0b46b952db4097be46855"}, + {file = "catboost-1.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:bfcb35e91b6fe42dcc92614d02f13dc47a4484ca5162de84a04c09244085d8d3"}, + {file = "catboost-1.2-cp310-cp310-win_amd64.whl", hash = "sha256:788e617712c8730e7edebac5c8efafd693097ba81b38c1992894e74702b8904e"}, + {file = "catboost-1.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:983c7ec6654baddf19c574a72cfa53e926e89e5ab48941b3bc94c06edca38a49"}, + {file = "catboost-1.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:96873ffbf8913457845926221e607aabfabcad2f40615a9d21a7ec1d65bcb8da"}, + {file = "catboost-1.2-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:a0236f6328e33f1e2e5fd6ca619ea5f8759ade06d709bd9c706443b20525fada"}, + {file = "catboost-1.2-cp311-cp311-win_amd64.whl", hash = "sha256:51d0a826b8d77ce4de43a861786a11aa3e6ea7a40b7e3ef5ae085893324d86db"}, + {file = "catboost-1.2-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:aaa3b90b76e7d6feff9955732b06fe3d2646e6bd5a0072d97ccc92fa8f6ef107"}, + {file = "catboost-1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf432f9d72b6c2f5cfa91b178e1e85cc627365dd90326ef2970e3ac12254e27f"}, + {file = "catboost-1.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:24346ceafcc3225b70ead73fbe19248c2869a4ef0d1d671bf9a8140ed4ab3c91"}, + {file = "catboost-1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:33283992ccd673e3e39d0f71d34c7249cc6deeb1a1e0abfef3d545405c29d417"}, + {file = "catboost-1.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:32b5577dbf509e0d736da9c6b63324a235a1d895b0873307e4a290c194fa5eac"}, + {file = "catboost-1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:475932765e16e812692b84bae5bfccb9612b12d52c8e936163fd9c70cb279c59"}, + {file = "catboost-1.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:30dd2241cf487eb3f06d7c33a807e2eb35b20367268161c08b4c047d7185a8a0"}, + {file = "catboost-1.2-cp38-cp38-win_amd64.whl", hash = "sha256:e616148b739527dcf02945744ea98ecb3c9619098f1b9bf4f83b77158a6fb764"}, + {file = "catboost-1.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:de0c55196ab95c6d0245d8c15cd8ec2b82eca6043c4554282809d0301e799654"}, + {file = "catboost-1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:601a9cdfd5fd8663da39c080213ac69241734b8d4120de3b0d60f3af35c1c4b0"}, + {file = "catboost-1.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:234ce778e278757ff7de2b5b2513953bf7877ceb1c477fe8176a279d24931520"}, + {file = "catboost-1.2-cp39-cp39-win_amd64.whl", hash = "sha256:bc67c9ccf1cd61a23eb0f52e255eb787c2752d7fd982c6e257c82a451260084b"}, + {file = "catboost-1.2.tar.gz", hash = "sha256:39e53403727ecfbb48156773ef3006b4e71cc35ab49cc9a0cf044b474c34be0c"}, ] [package.dependencies] graphviz = "*" matplotlib = "*" numpy = ">=1.16.0" -pandas = ">=0.24.0" +pandas = ">=0.24" plotly = "*" scipy = "*" six = "*" +[package.extras] +widget = ["ipython", "ipywidgets (>=7.0,<9.0)", "traitlets"] + [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] @@ -469,100 +494,87 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.0.1" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, - {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] [[package]] @@ -629,85 +641,65 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] development = ["black", "flake8", "mypy", "pytest", "types-colorama"] -[[package]] -name = "convertdate" -version = "2.4.0" -description = "Converts between Gregorian dates and other calendar systems" -category = "main" -optional = false -python-versions = "<4,>=3.7" -files = [ - {file = "convertdate-2.4.0-py3-none-any.whl", hash = "sha256:fcffe3a67522172648cf03b0c3757cfd079726fe5ae04ce29989ad3958039e4e"}, - {file = "convertdate-2.4.0.tar.gz", hash = "sha256:770c6b2195544d3e451e230b3f1c9b121ed02680b877f896306a04cf6f26b48f"}, -] - -[package.dependencies] -pymeeus = ">=0.3.13,<=1" - -[package.extras] -dev = ["black", "build", "isort", "pylint"] -docs = ["myst-parser", "sphinx", "sphinx-rtd-theme"] -tests = ["coverage"] - [[package]] name = "coverage" -version = "7.1.0" +version = "7.2.5" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, - {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, - {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, - {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, - {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, - {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, - {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, - {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, - {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, - {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, - {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, - {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, - {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, - {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, - {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, - {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, - {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, - {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, - {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, - {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, - {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, - {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, - {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, - {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, - {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, - {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, - {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, - {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, - {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, - {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, - {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, - {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, - {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, - {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, - {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, - {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, - {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, - {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, - {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, - {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, - {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, - {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, - {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, - {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, - {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, - {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, - {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, - {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, - {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, - {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, - {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, + {file = "coverage-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c"}, + {file = "coverage-7.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a"}, + {file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5"}, + {file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c"}, + {file = "coverage-7.2.5-cp310-cp310-win32.whl", hash = "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5"}, + {file = "coverage-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c"}, + {file = "coverage-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce"}, + {file = "coverage-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2"}, + {file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed"}, + {file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6"}, + {file = "coverage-7.2.5-cp311-cp311-win32.whl", hash = "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b"}, + {file = "coverage-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068"}, + {file = "coverage-7.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade"}, + {file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd"}, + {file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969"}, + {file = "coverage-7.2.5-cp37-cp37m-win32.whl", hash = "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718"}, + {file = "coverage-7.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0"}, + {file = "coverage-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84"}, + {file = "coverage-7.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045"}, + {file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd"}, + {file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1"}, + {file = "coverage-7.2.5-cp38-cp38-win32.whl", hash = "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813"}, + {file = "coverage-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212"}, + {file = "coverage-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b"}, + {file = "coverage-7.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e"}, + {file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a"}, + {file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1"}, + {file = "coverage-7.2.5-cp39-cp39-win32.whl", hash = "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31"}, + {file = "coverage-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252"}, + {file = "coverage-7.2.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3"}, + {file = "coverage-7.2.5.tar.gz", hash = "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"}, ] [package.extras] @@ -754,30 +746,30 @@ files = [ [[package]] name = "debugpy" -version = "1.6.6" +version = "1.6.7" description = "An implementation of the Debug Adapter Protocol for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "debugpy-1.6.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0ea1011e94416e90fb3598cc3ef5e08b0a4dd6ce6b9b33ccd436c1dffc8cd664"}, - {file = "debugpy-1.6.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dff595686178b0e75580c24d316aa45a8f4d56e2418063865c114eef651a982e"}, - {file = "debugpy-1.6.6-cp310-cp310-win32.whl", hash = "sha256:87755e173fcf2ec45f584bb9d61aa7686bb665d861b81faa366d59808bbd3494"}, - {file = "debugpy-1.6.6-cp310-cp310-win_amd64.whl", hash = "sha256:72687b62a54d9d9e3fb85e7a37ea67f0e803aaa31be700e61d2f3742a5683917"}, - {file = "debugpy-1.6.6-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:78739f77c58048ec006e2b3eb2e0cd5a06d5f48c915e2fc7911a337354508110"}, - {file = "debugpy-1.6.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23c29e40e39ad7d869d408ded414f6d46d82f8a93b5857ac3ac1e915893139ca"}, - {file = "debugpy-1.6.6-cp37-cp37m-win32.whl", hash = "sha256:7aa7e103610e5867d19a7d069e02e72eb2b3045b124d051cfd1538f1d8832d1b"}, - {file = "debugpy-1.6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:f6383c29e796203a0bba74a250615ad262c4279d398e89d895a69d3069498305"}, - {file = "debugpy-1.6.6-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:23363e6d2a04d726bbc1400bd4e9898d54419b36b2cdf7020e3e215e1dcd0f8e"}, - {file = "debugpy-1.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c"}, - {file = "debugpy-1.6.6-cp38-cp38-win32.whl", hash = "sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32"}, - {file = "debugpy-1.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225"}, - {file = "debugpy-1.6.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:11a0f3a106f69901e4a9a5683ce943a7a5605696024134b522aa1bfda25b5fec"}, - {file = "debugpy-1.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6"}, - {file = "debugpy-1.6.6-cp39-cp39-win32.whl", hash = "sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe"}, - {file = "debugpy-1.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1"}, - {file = "debugpy-1.6.6-py2.py3-none-any.whl", hash = "sha256:be596b44448aac14eb3614248c91586e2bc1728e020e82ef3197189aae556115"}, - {file = "debugpy-1.6.6.zip", hash = "sha256:b9c2130e1c632540fbf9c2c88341493797ddf58016e7cba02e311de9b0a96b67"}, + {file = "debugpy-1.6.7-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b3e7ac809b991006ad7f857f016fa92014445085711ef111fdc3f74f66144096"}, + {file = "debugpy-1.6.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3876611d114a18aafef6383695dfc3f1217c98a9168c1aaf1a02b01ec7d8d1e"}, + {file = "debugpy-1.6.7-cp310-cp310-win32.whl", hash = "sha256:33edb4afa85c098c24cc361d72ba7c21bb92f501104514d4ffec1fb36e09c01a"}, + {file = "debugpy-1.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:ed6d5413474e209ba50b1a75b2d9eecf64d41e6e4501977991cdc755dc83ab0f"}, + {file = "debugpy-1.6.7-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:38ed626353e7c63f4b11efad659be04c23de2b0d15efff77b60e4740ea685d07"}, + {file = "debugpy-1.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279d64c408c60431c8ee832dfd9ace7c396984fd7341fa3116aee414e7dcd88d"}, + {file = "debugpy-1.6.7-cp37-cp37m-win32.whl", hash = "sha256:dbe04e7568aa69361a5b4c47b4493d5680bfa3a911d1e105fbea1b1f23f3eb45"}, + {file = "debugpy-1.6.7-cp37-cp37m-win_amd64.whl", hash = "sha256:f90a2d4ad9a035cee7331c06a4cf2245e38bd7c89554fe3b616d90ab8aab89cc"}, + {file = "debugpy-1.6.7-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:5224eabbbeddcf1943d4e2821876f3e5d7d383f27390b82da5d9558fd4eb30a9"}, + {file = "debugpy-1.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae1123dff5bfe548ba1683eb972329ba6d646c3a80e6b4c06cd1b1dd0205e9b"}, + {file = "debugpy-1.6.7-cp38-cp38-win32.whl", hash = "sha256:9cd10cf338e0907fdcf9eac9087faa30f150ef5445af5a545d307055141dd7a4"}, + {file = "debugpy-1.6.7-cp38-cp38-win_amd64.whl", hash = "sha256:aaf6da50377ff4056c8ed470da24632b42e4087bc826845daad7af211e00faad"}, + {file = "debugpy-1.6.7-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:0679b7e1e3523bd7d7869447ec67b59728675aadfc038550a63a362b63029d2c"}, + {file = "debugpy-1.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de86029696e1b3b4d0d49076b9eba606c226e33ae312a57a46dca14ff370894d"}, + {file = "debugpy-1.6.7-cp39-cp39-win32.whl", hash = "sha256:d71b31117779d9a90b745720c0eab54ae1da76d5b38c8026c654f4a066b0130a"}, + {file = "debugpy-1.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:c0ff93ae90a03b06d85b2c529eca51ab15457868a377c4cc40a23ab0e4e552a3"}, + {file = "debugpy-1.6.7-py2.py3-none-any.whl", hash = "sha256:53f7a456bc50706a0eaabecf2d3ce44c4d5010e46dfc65b6b81a518b42866267"}, + {file = "debugpy-1.6.7.zip", hash = "sha256:c4c2f0810fa25323abfdfa36cbbbb24e5c3b1a42cb762782de64439c575d67f2"}, ] [[package]] @@ -857,14 +849,14 @@ files = [ [[package]] name = "fastjsonschema" -version = "2.16.2" +version = "2.16.3" description = "Fastest Python implementation of JSON schema" category = "dev" optional = false python-versions = "*" files = [ - {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, - {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, + {file = "fastjsonschema-2.16.3-py3-none-any.whl", hash = "sha256:04fbecc94300436f628517b05741b7ea009506ce8f946d40996567c669318490"}, + {file = "fastjsonschema-2.16.3.tar.gz", hash = "sha256:4a30d6315a68c253cfa8f963b9697246315aa3db89f98b97235e345dedfb0b8e"}, ] [package.extras] @@ -872,19 +864,19 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.9.0" +version = "3.12.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, - {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, + {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, + {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "flake8" @@ -1041,45 +1033,57 @@ test = ["objgraph", "psutil"] [[package]] name = "hijri-converter" -version = "2.2.4" +version = "2.3.1" description = "Accurate Hijri-Gregorian dates converter based on the Umm al-Qura calendar" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "hijri-converter-2.3.1.tar.gz", hash = "sha256:069b6789291e0830f4860a79dcd34fb3ceea3b95516ed4010202b9656ba1ab61"}, + {file = "hijri_converter-2.3.1-py3-none-any.whl", hash = "sha256:c04f072c78e1854a1f474ada253a3377009457221dbc1b14999761728e663662"}, +] + +[[package]] +name = "hnswlib" +version = "0.7.0" +description = "hnswlib" +category = "main" +optional = false +python-versions = "*" files = [ - {file = "hijri-converter-2.2.4.tar.gz", hash = "sha256:9e1d9fa4c220f6867da2abb1a96240675ae974abba951c686a781f4ef6ac218f"}, - {file = "hijri_converter-2.2.4-py3-none-any.whl", hash = "sha256:5ed4f4c284626e3916cd770e09346d5cc319e2a7762c22357838864908fd6e6d"}, + {file = "hnswlib-0.7.0.tar.gz", hash = "sha256:bc459668e7e44bb7454b256b90c98c5af750653919d9a91698dafcf416cf64c4"}, ] +[package.dependencies] +numpy = "*" + [[package]] name = "holidays" -version = "0.19" +version = "0.24" description = "Generate and work with holidays in Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "holidays-0.19-py3-none-any.whl", hash = "sha256:061d0512a574621de72a9d0e4ca363a1b9427211864203e405081f2d2cd192d0"}, - {file = "holidays-0.19.tar.gz", hash = "sha256:461028e65c7325f67a8933958f77045a5e41d5373611a56bbc267636d6fce87b"}, + {file = "holidays-0.24-py3-none-any.whl", hash = "sha256:7418c0ef1f5274849bc1e385e62f5a470b41839f8b45a798a087311b7966f03f"}, + {file = "holidays-0.24.tar.gz", hash = "sha256:484a1104b7a4248329d371d24dbae2289222c037194b4132c00d2076778936bd"}, ] [package.dependencies] -convertdate = ">=2.3.0" hijri-converter = "*" korean-lunar-calendar = "*" -PyMeeus = "*" python-dateutil = "*" [[package]] name = "identify" -version = "2.5.17" +version = "2.5.24" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, - {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, ] [package.extras] @@ -1177,14 +1181,14 @@ testing = ["importlib-resources (>=1.3)", "packaging", "pep517"] [[package]] name = "importlib-resources" -version = "5.10.2" +version = "5.12.0" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, - {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, ] [package.dependencies] @@ -1274,25 +1278,25 @@ files = [ [[package]] name = "ipywidgets" -version = "8.0.4" +version = "8.0.6" description = "Jupyter interactive widgets" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "ipywidgets-8.0.4-py3-none-any.whl", hash = "sha256:ebb195e743b16c3947fe8827190fb87b4d00979c0fbf685afe4d2c4927059fa1"}, - {file = "ipywidgets-8.0.4.tar.gz", hash = "sha256:c0005a77a47d77889cafed892b58e33b4a2a96712154404c6548ec22272811ea"}, + {file = "ipywidgets-8.0.6-py3-none-any.whl", hash = "sha256:a60bf8d2528997e05ac83fd19ea2fbe65f2e79fbe1b2b35779bdfc46c2941dcc"}, + {file = "ipywidgets-8.0.6.tar.gz", hash = "sha256:de7d779f2045d60de9f6c25f653fdae2dba57898e6a1284494b3ba20b6893bb8"}, ] [package.dependencies] ipykernel = ">=4.5.1" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0,<4.0" +jupyterlab-widgets = ">=3.0.7,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0,<5.0" +widgetsnbextension = ">=4.0.7,<4.1.0" [package.extras] -test = ["jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] [[package]] name = "isort" @@ -1460,25 +1464,28 @@ test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-com [[package]] name = "jupyter-console" -version = "6.4.4" +version = "6.6.3" description = "Jupyter terminal console" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "jupyter_console-6.4.4-py3-none-any.whl", hash = "sha256:756df7f4f60c986e7bc0172e4493d3830a7e6e75c08750bbe59c0a5403ad6dee"}, - {file = "jupyter_console-6.4.4.tar.gz", hash = "sha256:172f5335e31d600df61613a97b7f0352f2c8250bbd1092ef2d658f77249f89fb"}, + {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, + {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, ] [package.dependencies] -ipykernel = "*" +ipykernel = ">=6.14" ipython = "*" jupyter-client = ">=7.0.0" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +prompt-toolkit = ">=3.0.30" pygments = "*" +pyzmq = ">=17" +traitlets = ">=5.4" [package.extras] -test = ["pexpect"] +test = ["flaky", "pexpect", "pytest"] [[package]] name = "jupyter-core" @@ -1501,14 +1508,14 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "jupyter-server" -version = "1.23.5" +version = "1.24.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "jupyter_server-1.23.5-py3-none-any.whl", hash = "sha256:d42e520af98af9fbb7f0fb0d069991054d88e4d2ee051eb7110aef35f3756cef"}, - {file = "jupyter_server-1.23.5.tar.gz", hash = "sha256:a2caa759e2a29ece1aad013c58215fe951f38317b97de462b921834955fe3fca"}, + {file = "jupyter_server-1.24.0-py3-none-any.whl", hash = "sha256:c88ddbe862966ea1aea8c3ccb89a5903abd8fbcfe5cd14090ef549d403332c37"}, + {file = "jupyter_server-1.24.0.tar.gz", hash = "sha256:23368e8e214baf82b313d4c5a0d828ca73015e1a192ce3829bd74e62fab8d046"}, ] [package.dependencies] @@ -1599,14 +1606,14 @@ test = ["codecov", "ipykernel", "jupyter-server[test]", "openapi-core (>=0.14.2) [[package]] name = "jupyterlab-widgets" -version = "3.0.5" +version = "3.0.7" description = "Jupyter interactive widgets for JupyterLab" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "jupyterlab_widgets-3.0.5-py3-none-any.whl", hash = "sha256:a04a42e50231b355b7087e16a818f541e53589f7647144ea0344c4bf16f300e5"}, - {file = "jupyterlab_widgets-3.0.5.tar.gz", hash = "sha256:eeaecdeaf6c03afc960ddae201ced88d5979b4ca9c3891bcb8f6631af705f5ef"}, + {file = "jupyterlab_widgets-3.0.7-py3-none-any.whl", hash = "sha256:c73f8370338ec19f1bec47254752d6505b03601cbd5a67e6a0b184532f73a459"}, + {file = "jupyterlab_widgets-3.0.7.tar.gz", hash = "sha256:c3a50ed5bf528a0c7a869096503af54702f86dda1db469aee1c92dc0c01b43ca"}, ] [[package]] @@ -1793,13 +1800,13 @@ report = ["cffi (>=1.14.5,<2.0.0)", "weasyprint (>=52.5,<53.0)"] [[package]] name = "lightfm" -version = "1.16" +version = "1.17" description = "LightFM recommendation model" category = "main" optional = false python-versions = "*" files = [ - {file = "lightfm-1.16.tar.gz", hash = "sha256:41950fd8affde192c10b517148aa7f6d016ae2f75a2ec1187335f1c4d21876ac"}, + {file = "lightfm-1.17.tar.gz", hash = "sha256:2b77ada182ccd768a8d7643ab3cfcd8b6e855db09087f7cc7329bd63316697a8"}, ] [package.dependencies] @@ -1981,14 +1988,14 @@ testing = ["pytest"] [[package]] name = "markdown-it-py" -version = "2.1.0" +version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, - {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [package.dependencies] @@ -1996,10 +2003,10 @@ mdurl = ">=0.1,<1.0" typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code-style = ["pre-commit (==2.6)"] -compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] @@ -2119,7 +2126,6 @@ packaging = ">=20.0" pillow = ">=6.2.0" pyparsing = ">=2.2.1" python-dateutil = ">=2.7" -setuptools_scm = ">=4,<7" [[package]] name = "matplotlib-inline" @@ -2150,14 +2156,14 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.3.3" +version = "0.3.5" description = "Collection of plugins for markdown-it-py" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mdit-py-plugins-0.3.3.tar.gz", hash = "sha256:5cfd7e7ac582a594e23ba6546a2f406e94e42eb33ae596d0734781261c251260"}, - {file = "mdit_py_plugins-0.3.3-py3-none-any.whl", hash = "sha256:36d08a29def19ec43acdcd8ba471d3ebab132e7879d442760d963f19913e04b9"}, + {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, + {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, ] [package.dependencies] @@ -2194,14 +2200,14 @@ files = [ [[package]] name = "more-itertools" -version = "9.0.0" +version = "9.1.0" description = "More routines for operating on iterables, beyond itertools" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, - {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, + {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, + {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, ] [[package]] @@ -2245,14 +2251,14 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", [[package]] name = "nbclassic" -version = "0.5.1" +version = "1.0.0" description = "Jupyter Notebook as a Jupyter Server extension." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "nbclassic-0.5.1-py3-none-any.whl", hash = "sha256:32c235e1f22f4048f3b877d354c198202898797cf9c2085856827598cead001b"}, - {file = "nbclassic-0.5.1.tar.gz", hash = "sha256:8e8ffce7582bb7a4baf11fa86a3d88b184e8e7df78eed4ead69f15aa4fc0e323"}, + {file = "nbclassic-1.0.0-py3-none-any.whl", hash = "sha256:f99e4769b4750076cd4235c044b61232110733322384a94a63791d2e7beacc66"}, + {file = "nbclassic-1.0.0.tar.gz", hash = "sha256:0ae11eb2319455d805596bf320336cda9554b41d99ab9a3c31bf8180bffa30e3"}, ] [package.dependencies] @@ -2262,11 +2268,11 @@ ipython-genutils = "*" jinja2 = "*" jupyter-client = ">=6.1.1" jupyter-core = ">=4.6.1" -jupyter-server = ">=1.17.0" +jupyter-server = ">=1.8" nbconvert = ">=5" nbformat = "*" nest-asyncio = ">=1.5" -notebook-shim = ">=0.1.0" +notebook-shim = ">=0.2.3" prometheus-client = "*" pyzmq = ">=17" Send2Trash = ">=1.8.0" @@ -2281,14 +2287,14 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-p [[package]] name = "nbclient" -version = "0.7.2" +version = "0.7.4" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." category = "dev" optional = false python-versions = ">=3.7.0" files = [ - {file = "nbclient-0.7.2-py3-none-any.whl", hash = "sha256:d97ac6257de2794f5397609df754fcbca1a603e94e924eb9b99787c031ae2e7c"}, - {file = "nbclient-0.7.2.tar.gz", hash = "sha256:884a3f4a8c4fc24bb9302f263e0af47d97f0d01fe11ba714171b320c8ac09547"}, + {file = "nbclient-0.7.4-py3-none-any.whl", hash = "sha256:c817c0768c5ff0d60e468e017613e6eae27b6fa31e43f905addd2d24df60c125"}, + {file = "nbclient-0.7.4.tar.gz", hash = "sha256:d447f0e5a4cfe79d462459aec1b3dc5c2e9152597262be8ee27f7d4c02566a0d"}, ] [package.dependencies] @@ -2299,8 +2305,8 @@ traitlets = ">=5.3" [package.extras] dev = ["pre-commit"] -docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme"] -test = ["ipykernel", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] [[package]] name = "nbconvert" @@ -2392,6 +2398,56 @@ doc = ["nb2plots (>=0.6)", "numpydoc (>=1.1)", "pillow (>=8.2)", "pydata-sphinx- extra = ["lxml (>=4.5)", "pydot (>=1.4.1)", "pygraphviz (>=1.7)"] test = ["codecov (>=2.1)", "pytest (>=6.2)", "pytest-cov (>=2.12)"] +[[package]] +name = "nmslib" +version = "2.1.1" +description = "Non-Metric Space Library (NMSLIB)" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "nmslib-2.1.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:495ace1146bb1e89ef585a490fa65de19a87fa0d5bb029f3156402a431fc3558"}, + {file = "nmslib-2.1.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:c0ae48f01e63e70dc1e45b28cb1b1478ca09588a21127f1d4473afb22ff3bbbc"}, + {file = "nmslib-2.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:97b3f1af2c163369a043449dec7f05d681aa23c00f9d77bee09fd8f31ba41815"}, + {file = "nmslib-2.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:2061e94f980666bffff4f2173bf2b58a763062dee9cedab11ec46001e576c6b5"}, + {file = "nmslib-2.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e8b30bab631258b36e751d217180cdba0d2f942d779d4444cbc57a5173ee19ad"}, + {file = "nmslib-2.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:191a23dea20c0ad855e78df41a90273020f76d878c52aa1a80557ca83cf0f392"}, + {file = "nmslib-2.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:eae9a1d640a9e4c52c43272620f54f367f03ce25d8b07218fd90dfc204d5c49d"}, + {file = "nmslib-2.1.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:c22ffc3376d41b4b4e63d89ec5db2cf2e2051881827d658c94632800bed87e5b"}, + {file = "nmslib-2.1.1-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:a1ff4aeb143747106994a68dfcc29b097fc4562f632f2709f8f467b93d7181e2"}, + {file = "nmslib-2.1.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:5ce529d4f9b572135cdc5a18f04d3cbe258f1f440f8a06ab7209428a3b4c30d6"}, + {file = "nmslib-2.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:00f5b37305837fd5656ab4a65c4f0c9bc8e44cc9cf77cc9222f4153a72f0c727"}, + {file = "nmslib-2.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:08cb77c3fc2a346edc3407bd985b63a05bd0ec0d6d81ff2c6f7d6eb6bfbdbc1d"}, + {file = "nmslib-2.1.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:7dcc203450c9d95a551e9564646b9d0179edbde0874373ad63739b7e25a9980c"}, + {file = "nmslib-2.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db4b817f0896e0bbc836722c67b51bc237dc79477d2d383ced344d755705d409"}, + {file = "nmslib-2.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1b120f7766a52c4016c6b8889b34d97eddbb60a16fd39eb5a81e73ca101baf3e"}, + {file = "nmslib-2.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e5cd5d492bf7290adacac797ee092c863d230f3797d0db6a120a1a05c665be2"}, + {file = "nmslib-2.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0a80e574c3e6bfb724dbb77cbaec53cf9f4366382c2c0a9a2f5148c69b646afe"}, + {file = "nmslib-2.1.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:7b0d916f219b4044cd31e8f6bda92a7f23d68cb3f34f1c7da81e5852702d6ce0"}, + {file = "nmslib-2.1.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:a2129a7519ab73d3544ab6fa2f6c6c4c54644bf3d1c60d6929dcaac49c9307e6"}, + {file = "nmslib-2.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0f5c1c7e3c398a4ed7eaed65cd91776022b2ca4b8f15e504fa22c89da9333257"}, + {file = "nmslib-2.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c5a644d32ad3b2e24635a839d17d9d53d01a47ca55d1ba0d051f51f49df7e98a"}, + {file = "nmslib-2.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fc75c7e128fd19c21c8cd73a1681a32d902bf5203699321cdfff8cf98607c82d"}, + {file = "nmslib-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:43118bb64f06e62662a8c7f1fe54ae699a078b438e3f8cb75db547731a5e73f6"}, + {file = "nmslib-2.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:1b87f7a5813cabedef47b289337ab1eefa4e62485a05884c2dbe7ba1d9c327a4"}, + {file = "nmslib-2.1.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ca3d4eb91232078fb96eaa938a4dcbcfb3da48a7800350e1ded70d8d3e6e65a5"}, + {file = "nmslib-2.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b6ebb53999672abf7811ef35e2ec3d76b2a845fd5dbaa5482e2cc445481d87cf"}, + {file = "nmslib-2.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:26062f5f172c35038c46feabb5c60c104808c167db0c3dfab19d11eb99f18b85"}, + {file = "nmslib-2.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ed63fda2a5572391a0a540689f00d390040b9255e08012ea43344958cea92f36"}, + {file = "nmslib-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:6a3aa123d85c188fc64cb8957e5cdded9c8c57bea2c6a5866fe06c729e6e6e85"}, + {file = "nmslib-2.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:3fe5bbf304fdde50f3a55329efcf26d4ba4eb1181885877a1baab1df900dd671"}, + {file = "nmslib-2.1.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:7b5a561a71dce6d56b32ea89a591dbe477b4173fbc3e9a1153d534bc248ae7b5"}, + {file = "nmslib-2.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:82680077a06a817df95ea716260231245cf077a4c08b0f39148d4a58d2eb58f2"}, + {file = "nmslib-2.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be54644e1a1a6e2539e565fe24415997f18656a5260276c40266cacb70e2a9ab"}, + {file = "nmslib-2.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3681e3e9160fa9661192c19202402e22151e016cf365621609ae5ca4a4677d21"}, + {file = "nmslib-2.1.1.tar.gz", hash = "sha256:971294a7766f91c697d63c88c8adda4797a944346cc535a217393f4167a19c20"}, +] + +[package.dependencies] +numpy = {version = ">=1.10.0", markers = "python_version >= \"3.5\""} +psutil = "*" +pybind11 = "<2.6.2" + [[package]] name = "nodeenv" version = "1.7.0" @@ -2409,14 +2465,14 @@ setuptools = "*" [[package]] name = "notebook" -version = "6.5.2" +version = "6.5.4" description = "A web-based notebook environment for interactive computing" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "notebook-6.5.2-py3-none-any.whl", hash = "sha256:e04f9018ceb86e4fa841e92ea8fb214f8d23c1cedfde530cc96f92446924f0e4"}, - {file = "notebook-6.5.2.tar.gz", hash = "sha256:c1897e5317e225fc78b45549a6ab4b668e4c996fd03a04e938fe5e7af2bfffd0"}, + {file = "notebook-6.5.4-py3-none-any.whl", hash = "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f"}, + {file = "notebook-6.5.4.tar.gz", hash = "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e"}, ] [package.dependencies] @@ -2444,21 +2500,21 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixs [[package]] name = "notebook-shim" -version = "0.2.2" +version = "0.2.3" description = "A shim layer for notebook traits and config" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, - {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, + {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, + {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, ] [package.dependencies] jupyter-server = ">=1.8,<3" [package.extras] -test = ["pytest", "pytest-console-scripts", "pytest-tornasync"] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] [[package]] name = "numba" @@ -2547,14 +2603,14 @@ files = [ [[package]] name = "optuna" -version = "3.1.0" +version = "3.1.1" description = "A hyperparameter optimization framework" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "optuna-3.1.0-py3-none-any.whl", hash = "sha256:f79e2c2747bbf2779b1ab21de0ff553218159c36695326e8d6f2889db7d5c2a0"}, - {file = "optuna-3.1.0.tar.gz", hash = "sha256:96c7c92860c8692d3aa569d749e72b121422cb4af0ed3ad4bfbc445b61416919"}, + {file = "optuna-3.1.1-py3-none-any.whl", hash = "sha256:4d641b4bdd896f48a766803a5b64286281fa3e5dbbeedc549f8e1ee6c6e3eea8"}, + {file = "optuna-3.1.1.tar.gz", hash = "sha256:f3c8fcdb7ed4dd473485f6f61de2f2ceb9d4ad56afbae42e663e15c3f00a4c10"}, ] [package.dependencies] @@ -2577,14 +2633,14 @@ test = ["codecov", "fakeredis[lua]", "kaleido", "pytest", "scipy (>=1.9.2)"] [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] @@ -2674,14 +2730,14 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.0" +version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, - {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] [[package]] @@ -2732,93 +2788,82 @@ files = [ [[package]] name = "pillow" -version = "9.4.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, - {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, - {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, - {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, - {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, - {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, - {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, - {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, - {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, - {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, - {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, - {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, - {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, - {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, - {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, - {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, - {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, - {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, - {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, - {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, - {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, - {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, - {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, - {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, - {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, - {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] @@ -2835,36 +2880,37 @@ files = [ [[package]] name = "platformdirs" -version = "3.0.0" +version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"}, - {file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"}, + {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, + {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, ] [package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "plotly" -version = "5.13.0" +version = "5.14.1" description = "An open-source, interactive data visualization library for Python" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "plotly-5.13.0-py2.py3-none-any.whl", hash = "sha256:4ac5db72176ce144f1fcde8d1ef7bdbccf5bb7a53e3d366b16fcd7c85319fdfd"}, - {file = "plotly-5.13.0.tar.gz", hash = "sha256:81a3aae4021d5ab91790fc71c3433791f41bfc71586e857f7777f429a955039a"}, + {file = "plotly-5.14.1-py2.py3-none-any.whl", hash = "sha256:a63f3ad9e4cc2e02902a738e5e3e7f3d1307f2732ac71a6c28f1238ed3052826"}, + {file = "plotly-5.14.1.tar.gz", hash = "sha256:bcac86d7fcba3eff7260c1eddc36ca34dae2aded10a0709808446565e0e53b93"}, ] [package.dependencies] +packaging = "*" tenacity = ">=6.2.0" [[package]] @@ -2887,14 +2933,14 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.5.0" +version = "1.5.2" description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_core-1.5.0-py3-none-any.whl", hash = "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84"}, - {file = "poetry_core-1.5.0.tar.gz", hash = "sha256:253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a"}, + {file = "poetry_core-1.5.2-py3-none-any.whl", hash = "sha256:832d40a1ea5fd10c0f648d0575cadddc8b79f06f91d83a1f1a73a7e1dfacfbd7"}, + {file = "poetry_core-1.5.2.tar.gz", hash = "sha256:c6556c3b1ec5b8668e6ef5a4494726bc41d31907339425e194e78a6178436c14"}, ] [package.dependencies] @@ -2938,14 +2984,14 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.36" +version = "3.0.38" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, - {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, + {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, + {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, ] [package.dependencies] @@ -2953,26 +2999,26 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.4" +version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, ] [package.extras] @@ -3016,42 +3062,57 @@ files = [ [[package]] name = "pyarrow" -version = "11.0.0" +version = "12.0.0" description = "Python library for Apache Arrow" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pyarrow-11.0.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:40bb42afa1053c35c749befbe72f6429b7b5f45710e85059cdd534553ebcf4f2"}, - {file = "pyarrow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c28b5f248e08dea3b3e0c828b91945f431f4202f1a9fe84d1012a761324e1ba"}, - {file = "pyarrow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37bc81f6c9435da3c9c1e767324ac3064ffbe110c4e460660c43e144be4ed85"}, - {file = "pyarrow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7c53def8dbbc810282ad308cc46a523ec81e653e60a91c609c2233ae407689"}, - {file = "pyarrow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:25aa11c443b934078bfd60ed63e4e2d42461682b5ac10f67275ea21e60e6042c"}, - {file = "pyarrow-11.0.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:e217d001e6389b20a6759392a5ec49d670757af80101ee6b5f2c8ff0172e02ca"}, - {file = "pyarrow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad42bb24fc44c48f74f0d8c72a9af16ba9a01a2ccda5739a517aa860fa7e3d56"}, - {file = "pyarrow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d942c690ff24a08b07cb3df818f542a90e4d359381fbff71b8f2aea5bf58841"}, - {file = "pyarrow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f010ce497ca1b0f17a8243df3048055c0d18dcadbcc70895d5baf8921f753de5"}, - {file = "pyarrow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:2f51dc7ca940fdf17893227edb46b6784d37522ce08d21afc56466898cb213b2"}, - {file = "pyarrow-11.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:1cbcfcbb0e74b4d94f0b7dde447b835a01bc1d16510edb8bb7d6224b9bf5bafc"}, - {file = "pyarrow-11.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaee8f79d2a120bf3e032d6d64ad20b3af6f56241b0ffc38d201aebfee879d00"}, - {file = "pyarrow-11.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:410624da0708c37e6a27eba321a72f29d277091c8f8d23f72c92bada4092eb5e"}, - {file = "pyarrow-11.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2d53ba72917fdb71e3584ffc23ee4fcc487218f8ff29dd6df3a34c5c48fe8c06"}, - {file = "pyarrow-11.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:f12932e5a6feb5c58192209af1d2607d488cb1d404fbc038ac12ada60327fa34"}, - {file = "pyarrow-11.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:41a1451dd895c0b2964b83d91019e46f15b5564c7ecd5dcb812dadd3f05acc97"}, - {file = "pyarrow-11.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc2344be80e5dce4e1b80b7c650d2fc2061b9eb339045035a1baa34d5b8f1c"}, - {file = "pyarrow-11.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f40be0d7381112a398b93c45a7e69f60261e7b0269cc324e9f739ce272f4f70"}, - {file = "pyarrow-11.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:362a7c881b32dc6b0eccf83411a97acba2774c10edcec715ccaab5ebf3bb0835"}, - {file = "pyarrow-11.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ccbf29a0dadfcdd97632b4f7cca20a966bb552853ba254e874c66934931b9841"}, - {file = "pyarrow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e99be85973592051e46412accea31828da324531a060bd4585046a74ba45854"}, - {file = "pyarrow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69309be84dcc36422574d19c7d3a30a7ea43804f12552356d1ab2a82a713c418"}, - {file = "pyarrow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da93340fbf6f4e2a62815064383605b7ffa3e9eeb320ec839995b1660d69f89b"}, - {file = "pyarrow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:caad867121f182d0d3e1a0d36f197df604655d0b466f1bc9bafa903aa95083e4"}, - {file = "pyarrow-11.0.0.tar.gz", hash = "sha256:5461c57dbdb211a632a48facb9b39bbeb8a7905ec95d768078525283caef5f6d"}, + {file = "pyarrow-12.0.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:3b97649c8a9a09e1d8dc76513054f1331bd9ece78ee39365e6bf6bc7503c1e94"}, + {file = "pyarrow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bc4ea634dacb03936f50fcf59574a8e727f90c17c24527e488d8ceb52ae284de"}, + {file = "pyarrow-12.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d568acfca3faa565d663e53ee34173be8e23a95f78f2abfdad198010ec8f745"}, + {file = "pyarrow-12.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b50bb9a82dca38a002d7cbd802a16b1af0f8c50ed2ec94a319f5f2afc047ee9"}, + {file = "pyarrow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:3d1733b1ea086b3c101427d0e57e2be3eb964686e83c2363862a887bb5c41fa8"}, + {file = "pyarrow-12.0.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:a7cd32fe77f967fe08228bc100433273020e58dd6caced12627bcc0a7675a513"}, + {file = "pyarrow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92fb031e6777847f5c9b01eaa5aa0c9033e853ee80117dce895f116d8b0c3ca3"}, + {file = "pyarrow-12.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:280289ebfd4ac3570f6b776515baa01e4dcbf17122c401e4b7170a27c4be63fd"}, + {file = "pyarrow-12.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272f147d4f8387bec95f17bb58dcfc7bc7278bb93e01cb7b08a0e93a8921e18e"}, + {file = "pyarrow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:0846ace49998825eda4722f8d7f83fa05601c832549c9087ea49d6d5397d8cec"}, + {file = "pyarrow-12.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:993287136369aca60005ee7d64130f9466489c4f7425f5c284315b0a5401ccd9"}, + {file = "pyarrow-12.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7b6a765ee4f88efd7d8348d9a1f804487d60799d0428b6ddf3344eaef37282"}, + {file = "pyarrow-12.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c4fce253d5bdc8d62f11cfa3da5b0b34b562c04ce84abb8bd7447e63c2b327"}, + {file = "pyarrow-12.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e6be4d85707fc8e7a221c8ab86a40449ce62559ce25c94321df7c8500245888f"}, + {file = "pyarrow-12.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:ea830d9f66bfb82d30b5794642f83dd0e4a718846462d22328981e9eb149cba8"}, + {file = "pyarrow-12.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7b5b9f60d9ef756db59bec8d90e4576b7df57861e6a3d6a8bf99538f68ca15b3"}, + {file = "pyarrow-12.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99e559d27db36ad3a33868a475f03e3129430fc065accc839ef4daa12c6dab6"}, + {file = "pyarrow-12.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b0810864a593b89877120972d1f7af1d1c9389876dbed92b962ed81492d3ffc"}, + {file = "pyarrow-12.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:23a77d97f4d101ddfe81b9c2ee03a177f0e590a7e68af15eafa06e8f3cf05976"}, + {file = "pyarrow-12.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2cc63e746221cddb9001f7281dee95fd658085dd5b717b076950e1ccc607059c"}, + {file = "pyarrow-12.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8c26912607e26c2991826bbaf3cf2b9c8c3e17566598c193b492f058b40d3a4"}, + {file = "pyarrow-12.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d8b90efc290e99a81d06015f3a46601c259ecc81ffb6d8ce288c91bd1b868c9"}, + {file = "pyarrow-12.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2466be046b81863be24db370dffd30a2e7894b4f9823fb60ef0a733c31ac6256"}, + {file = "pyarrow-12.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:0e36425b1c1cbf5447718b3f1751bf86c58f2b3ad299f996cd9b1aa040967656"}, + {file = "pyarrow-12.0.0.tar.gz", hash = "sha256:19c812d303610ab5d664b7b1de4051ae23565f9f94d04cbea9e50569746ae1ee"}, ] [package.dependencies] numpy = ">=1.16.6" +[[package]] +name = "pybind11" +version = "2.6.1" +description = "Seamless operability between C++11 and Python" +category = "main" +optional = false +python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7" +files = [ + {file = "pybind11-2.6.1-py2.py3-none-any.whl", hash = "sha256:c3691da74b670a4850dec30c1145a0dad53a50eeca78b7e7cdc855b5c98fd32d"}, + {file = "pybind11-2.6.1.tar.gz", hash = "sha256:ab7e60a520fe6ae25eca939191bb2ac416cd58478ce754740238a8bf1af18934"}, +] + +[package.extras] +global = ["pybind11-global (==2.6.1)"] + [[package]] name = "pycodestyle" version = "2.9.1" @@ -3090,14 +3151,14 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] @@ -3128,17 +3189,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] testutil = ["gitpython (>3)"] -[[package]] -name = "pymeeus" -version = "0.5.12" -description = "Python implementation of Jean Meeus astronomical routines" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "PyMeeus-0.5.12.tar.gz", hash = "sha256:548f7186bd8b96cbc069cf649a8e8e377dce49ac74486709849fe63a99cad684"}, -] - [[package]] name = "pyparsing" version = "3.0.9" @@ -3324,38 +3374,38 @@ torch = "*" [[package]] name = "pytz" -version = "2022.7.1" +version = "2023.3" description = "World timezone definitions, modern and historical" category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, - {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] name = "pywin32" -version = "305" +version = "306" description = "Python for Window Extensions" category = "dev" optional = false python-versions = "*" files = [ - {file = "pywin32-305-cp310-cp310-win32.whl", hash = "sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116"}, - {file = "pywin32-305-cp310-cp310-win_amd64.whl", hash = "sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478"}, - {file = "pywin32-305-cp310-cp310-win_arm64.whl", hash = "sha256:742eb905ce2187133a29365b428e6c3b9001d79accdc30aa8969afba1d8470f4"}, - {file = "pywin32-305-cp311-cp311-win32.whl", hash = "sha256:19ca459cd2e66c0e2cc9a09d589f71d827f26d47fe4a9d09175f6aa0256b51c2"}, - {file = "pywin32-305-cp311-cp311-win_amd64.whl", hash = "sha256:326f42ab4cfff56e77e3e595aeaf6c216712bbdd91e464d167c6434b28d65990"}, - {file = "pywin32-305-cp311-cp311-win_arm64.whl", hash = "sha256:4ecd404b2c6eceaca52f8b2e3e91b2187850a1ad3f8b746d0796a98b4cea04db"}, - {file = "pywin32-305-cp36-cp36m-win32.whl", hash = "sha256:48d8b1659284f3c17b68587af047d110d8c44837736b8932c034091683e05863"}, - {file = "pywin32-305-cp36-cp36m-win_amd64.whl", hash = "sha256:13362cc5aa93c2beaf489c9c9017c793722aeb56d3e5166dadd5ef82da021fe1"}, - {file = "pywin32-305-cp37-cp37m-win32.whl", hash = "sha256:a55db448124d1c1484df22fa8bbcbc45c64da5e6eae74ab095b9ea62e6d00496"}, - {file = "pywin32-305-cp37-cp37m-win_amd64.whl", hash = "sha256:109f98980bfb27e78f4df8a51a8198e10b0f347257d1e265bb1a32993d0c973d"}, - {file = "pywin32-305-cp38-cp38-win32.whl", hash = "sha256:9dd98384da775afa009bc04863426cb30596fd78c6f8e4e2e5bbf4edf8029504"}, - {file = "pywin32-305-cp38-cp38-win_amd64.whl", hash = "sha256:56d7a9c6e1a6835f521788f53b5af7912090674bb84ef5611663ee1595860fc7"}, - {file = "pywin32-305-cp39-cp39-win32.whl", hash = "sha256:9d968c677ac4d5cbdaa62fd3014ab241718e619d8e36ef8e11fb930515a1e918"}, - {file = "pywin32-305-cp39-cp39-win_amd64.whl", hash = "sha256:50768c6b7c3f0b38b7fb14dd4104da93ebced5f1a50dc0e834594bff6fbe1271"}, + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] [[package]] @@ -3426,89 +3476,89 @@ files = [ [[package]] name = "pyzmq" -version = "25.0.0" +version = "25.0.2" description = "Python bindings for 0MQ" category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:2d05d904f03ddf1e0d83d97341354dfe52244a619b5a1440a5f47a5b3451e84e"}, - {file = "pyzmq-25.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a154ef810d44f9d28868be04641f837374a64e7449df98d9208e76c260c7ef1"}, - {file = "pyzmq-25.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:487305c2a011fdcf3db1f24e8814bb76d23bc4d2f46e145bc80316a59a9aa07d"}, - {file = "pyzmq-25.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e7b87638ee30ab13230e37ce5331b3e730b1e0dda30120b9eeec3540ed292c8"}, - {file = "pyzmq-25.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75243e422e85a62f0ab7953dc315452a56b2c6a7e7d1a3c3109ac3cc57ed6b47"}, - {file = "pyzmq-25.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:31e523d067ce44a04e876bed3ff9ea1ff8d1b6636d16e5fcace9d22f8c564369"}, - {file = "pyzmq-25.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8539216173135e9e89f6b1cc392e74e6b935b91e8c76106cf50e7a02ab02efe5"}, - {file = "pyzmq-25.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2754fa68da08a854f4816e05160137fa938a2347276471103d31e04bcee5365c"}, - {file = "pyzmq-25.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1bc30f0c18444d51e9b0d0dd39e3a4e7c53ee74190bebef238cd58de577ea9"}, - {file = "pyzmq-25.0.0-cp310-cp310-win32.whl", hash = "sha256:01d53958c787cfea34091fcb8ef36003dbb7913b8e9f8f62a0715234ebc98b70"}, - {file = "pyzmq-25.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:58fc3ad5e1cfd2e6d24741fbb1e216b388115d31b0ca6670f894187f280b6ba6"}, - {file = "pyzmq-25.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:e4bba04ea779a3d7ef25a821bb63fd0939142c88e7813e5bd9c6265a20c523a2"}, - {file = "pyzmq-25.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af1fbfb7ad6ac0009ccee33c90a1d303431c7fb594335eb97760988727a37577"}, - {file = "pyzmq-25.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85456f0d8f3268eecd63dede3b99d5bd8d3b306310c37d4c15141111d22baeaf"}, - {file = "pyzmq-25.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0645b5a2d2a06fd8eb738018490c514907f7488bf9359c6ee9d92f62e844b76f"}, - {file = "pyzmq-25.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f72ea279b2941a5203e935a4588b9ba8a48aeb9a926d9dfa1986278bd362cb8"}, - {file = "pyzmq-25.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4e295f7928a31ae0f657e848c5045ba6d693fe8921205f408ca3804b1b236968"}, - {file = "pyzmq-25.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ac97e7d647d5519bcef48dd8d3d331f72975afa5c4496c95f6e854686f45e2d9"}, - {file = "pyzmq-25.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:656281d496aaf9ca4fd4cea84e6d893e3361057c4707bd38618f7e811759103c"}, - {file = "pyzmq-25.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f6116991568aac48b94d6d8aaed6157d407942ea385335a6ed313692777fb9d"}, - {file = "pyzmq-25.0.0-cp311-cp311-win32.whl", hash = "sha256:0282bba9aee6e0346aa27d6c69b5f7df72b5a964c91958fc9e0c62dcae5fdcdc"}, - {file = "pyzmq-25.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:526f884a27e8bba62fe1f4e07c62be2cfe492b6d432a8fdc4210397f8cf15331"}, - {file = "pyzmq-25.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ccb3e1a863222afdbda42b7ca8ac8569959593d7abd44f5a709177d6fa27d266"}, - {file = "pyzmq-25.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4046d03100aca266e70d54a35694cb35d6654cfbef633e848b3c4a8d64b9d187"}, - {file = "pyzmq-25.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3100dddcada66ec5940ed6391ebf9d003cc3ede3d320748b2737553019f58230"}, - {file = "pyzmq-25.0.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7877264aa851c19404b1bb9dbe6eed21ea0c13698be1eda3784aab3036d1c861"}, - {file = "pyzmq-25.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5049e75cc99db65754a3da5f079230fb8889230cf09462ec972d884d1704a3ed"}, - {file = "pyzmq-25.0.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:81f99fb1224d36eb91557afec8cdc2264e856f3464500b55749020ce4c848ef2"}, - {file = "pyzmq-25.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a1cd4a95f176cdc0ee0a82d49d5830f13ae6015d89decbf834c273bc33eeb3d3"}, - {file = "pyzmq-25.0.0-cp36-cp36m-win32.whl", hash = "sha256:926236ca003aec70574754f39703528947211a406f5c6c8b3e50eca04a9e87fc"}, - {file = "pyzmq-25.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:94f0a7289d0f5c80807c37ebb404205e7deb737e8763eb176f4770839ee2a287"}, - {file = "pyzmq-25.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f3f96d452e9580cb961ece2e5a788e64abaecb1232a80e61deffb28e105ff84a"}, - {file = "pyzmq-25.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:930e6ad4f2eaac31a3d0c2130619d25db754b267487ebc186c6ad18af2a74018"}, - {file = "pyzmq-25.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1081d7030a1229c8ff90120346fb7599b54f552e98fcea5170544e7c6725aab"}, - {file = "pyzmq-25.0.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:531866c491aee5a1e967c286cfa470dffac1e2a203b1afda52d62b58782651e9"}, - {file = "pyzmq-25.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fc7c1421c5b1c916acf3128bf3cc7ea7f5018b58c69a6866d70c14190e600ce9"}, - {file = "pyzmq-25.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9a2d5e419bd39a1edb6cdd326d831f0120ddb9b1ff397e7d73541bf393294973"}, - {file = "pyzmq-25.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:183e18742be3621acf8908903f689ec520aee3f08449bfd29f583010ca33022b"}, - {file = "pyzmq-25.0.0-cp37-cp37m-win32.whl", hash = "sha256:02f5cb60a7da1edd5591a15efa654ffe2303297a41e1b40c3c8942f8f11fc17c"}, - {file = "pyzmq-25.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:cac602e02341eaaf4edfd3e29bd3fdef672e61d4e6dfe5c1d065172aee00acee"}, - {file = "pyzmq-25.0.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:e14df47c1265356715d3d66e90282a645ebc077b70b3806cf47efcb7d1d630cb"}, - {file = "pyzmq-25.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:293a7c2128690f496057f1f1eb6074f8746058d13588389981089ec45d8fdc77"}, - {file = "pyzmq-25.0.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:731b208bc9412deeb553c9519dca47136b5a01ca66667cafd8733211941b17e4"}, - {file = "pyzmq-25.0.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b055a1cddf8035966ad13aa51edae5dc8f1bba0b5d5e06f7a843d8b83dc9b66b"}, - {file = "pyzmq-25.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17e1cb97d573ea84d7cd97188b42ca6f611ab3ee600f6a75041294ede58e3d20"}, - {file = "pyzmq-25.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:60ecbfe7669d3808ffa8a7dd1487d6eb8a4015b07235e3b723d4b2a2d4de7203"}, - {file = "pyzmq-25.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c25c95416133942280faaf068d0fddfd642b927fb28aaf4ab201a738e597c1e"}, - {file = "pyzmq-25.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:be05504af0619d1cffa500af1e0ede69fb683f301003851f5993b5247cc2c576"}, - {file = "pyzmq-25.0.0-cp38-cp38-win32.whl", hash = "sha256:6bf3842af37af43fa953e96074ebbb5315f6a297198f805d019d788a1021dbc8"}, - {file = "pyzmq-25.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b90bb8dfbbd138558f1f284fecfe328f7653616ff9a972433a00711d9475d1a9"}, - {file = "pyzmq-25.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:62b9e80890c0d2408eb42d5d7e1fc62a5ce71be3288684788f74cf3e59ffd6e2"}, - {file = "pyzmq-25.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484c2c4ee02c1edc07039f42130bd16e804b1fe81c4f428e0042e03967f40c20"}, - {file = "pyzmq-25.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9ca6db34b26c4d3e9b0728841ec9aa39484eee272caa97972ec8c8e231b20c7e"}, - {file = "pyzmq-25.0.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:610d2d112acd4e5501fac31010064a6c6efd716ceb968e443cae0059eb7b86de"}, - {file = "pyzmq-25.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3594c0ff604e685d7e907860b61d0e10e46c74a9ffca168f6e9e50ea934ee440"}, - {file = "pyzmq-25.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c21a5f4e54a807df5afdef52b6d24ec1580153a6bcf0607f70a6e1d9fa74c5c3"}, - {file = "pyzmq-25.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4725412e27612f0d7d7c2f794d89807ad0227c2fc01dd6146b39ada49c748ef9"}, - {file = "pyzmq-25.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d3d604fe0a67afd1aff906e54da557a5203368a99dcc50a70eef374f1d2abef"}, - {file = "pyzmq-25.0.0-cp39-cp39-win32.whl", hash = "sha256:3670e8c5644768f214a3b598fe46378a4a6f096d5fb82a67dfd3440028460565"}, - {file = "pyzmq-25.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:e99629a976809fe102ef73e856cf4b2660acd82a412a51e80ba2215e523dfd0a"}, - {file = "pyzmq-25.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:66509c48f7446b640eeae24b60c9c1461799a27b1b0754e438582e36b5af3315"}, - {file = "pyzmq-25.0.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c464cc508177c09a5a6122b67f978f20e2954a21362bf095a0da4647e3e908"}, - {file = "pyzmq-25.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:28bcb2e66224a7ac2843eb632e4109d6b161479e7a2baf24e37210461485b4f1"}, - {file = "pyzmq-25.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0e7ef9ac807db50b4eb6f534c5dcc22f998f5dae920cc28873d2c1d080a4fc9"}, - {file = "pyzmq-25.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5050f5c50b58a6e38ccaf9263a356f74ef1040f5ca4030225d1cb1a858c5b7b6"}, - {file = "pyzmq-25.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2a73af6504e0d2805e926abf136ebf536735a13c22f709be7113c2ec65b4bec3"}, - {file = "pyzmq-25.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0e8d00228db627ddd1b418c7afd81820b38575f237128c9650365f2dd6ac3443"}, - {file = "pyzmq-25.0.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5605621f2181f20b71f13f698944deb26a0a71af4aaf435b34dd90146092d530"}, - {file = "pyzmq-25.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6136bfb0e5a9cf8c60c6ac763eb21f82940a77e6758ea53516c8c7074f4ff948"}, - {file = "pyzmq-25.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0a90b2480a26aef7c13cff18703ba8d68e181facb40f78873df79e6d42c1facc"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00c94fd4c9dd3c95aace0c629a7fa713627a5c80c1819326b642adf6c4b8e2a2"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20638121b0bdc80777ce0ec8c1f14f1ffec0697a1f88f0b564fa4a23078791c4"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f75b4b8574f3a8a0d6b4b52606fc75b82cb4391471be48ab0b8677c82f9ed4"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cbb885f347eba7ab7681c450dee5b14aed9f153eec224ec0c3f299273d9241f"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c48f257da280b3be6c94e05bd575eddb1373419dbb1a72c3ce64e88f29d1cd6d"}, - {file = "pyzmq-25.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:866eabf7c1315ef2e93e34230db7cbf672e0d7c626b37c11f7e870c8612c3dcc"}, - {file = "pyzmq-25.0.0.tar.gz", hash = "sha256:f330a1a2c7f89fd4b0aa4dcb7bf50243bf1c8da9a2f1efc31daf57a2046b31f2"}, + {file = "pyzmq-25.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ac178e666c097c8d3deb5097b58cd1316092fc43e8ef5b5fdb259b51da7e7315"}, + {file = "pyzmq-25.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:659e62e1cbb063151c52f5b01a38e1df6b54feccfa3e2509d44c35ca6d7962ee"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8280ada89010735a12b968ec3ea9a468ac2e04fddcc1cede59cb7f5178783b9c"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b5eeb5278a8a636bb0abdd9ff5076bcbb836cd2302565df53ff1fa7d106d54"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a2e5fe42dfe6b73ca120b97ac9f34bfa8414feb15e00e37415dbd51cf227ef6"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:827bf60e749e78acb408a6c5af6688efbc9993e44ecc792b036ec2f4b4acf485"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b504ae43d37e282301da586529e2ded8b36d4ee2cd5e6db4386724ddeaa6bbc"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb1f69a0a2a2b1aae8412979dd6293cc6bcddd4439bf07e4758d864ddb112354"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b9c9cc965cdf28381e36da525dcb89fc1571d9c54800fdcd73e3f73a2fc29bd"}, + {file = "pyzmq-25.0.2-cp310-cp310-win32.whl", hash = "sha256:24abbfdbb75ac5039205e72d6c75f10fc39d925f2df8ff21ebc74179488ebfca"}, + {file = "pyzmq-25.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6a821a506822fac55d2df2085a52530f68ab15ceed12d63539adc32bd4410f6e"}, + {file = "pyzmq-25.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9af0bb0277e92f41af35e991c242c9c71920169d6aa53ade7e444f338f4c8128"}, + {file = "pyzmq-25.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54a96cf77684a3a537b76acfa7237b1e79a8f8d14e7f00e0171a94b346c5293e"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88649b19ede1cab03b96b66c364cbbf17c953615cdbc844f7f6e5f14c5e5261c"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:715cff7644a80a7795953c11b067a75f16eb9fc695a5a53316891ebee7f3c9d5"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b3f0f066b4f1d17383aae509bacf833ccaf591184a1f3c7a1661c085063ae"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d488c5c8630f7e782e800869f82744c3aca4aca62c63232e5d8c490d3d66956a"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:38d9f78d69bcdeec0c11e0feb3bc70f36f9b8c44fc06e5d06d91dc0a21b453c7"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3059a6a534c910e1d5d068df42f60d434f79e6cc6285aa469b384fa921f78cf8"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6526d097b75192f228c09d48420854d53dfbc7abbb41b0e26f363ccb26fbc177"}, + {file = "pyzmq-25.0.2-cp311-cp311-win32.whl", hash = "sha256:5c5fbb229e40a89a2fe73d0c1181916f31e30f253cb2d6d91bea7927c2e18413"}, + {file = "pyzmq-25.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed15e3a2c3c2398e6ae5ce86d6a31b452dfd6ad4cd5d312596b30929c4b6e182"}, + {file = "pyzmq-25.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:032f5c8483c85bf9c9ca0593a11c7c749d734ce68d435e38c3f72e759b98b3c9"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:374b55516393bfd4d7a7daa6c3b36d6dd6a31ff9d2adad0838cd6a203125e714"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08bfcc21b5997a9be4fefa405341320d8e7f19b4d684fb9c0580255c5bd6d695"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1a843d26a8da1b752c74bc019c7b20e6791ee813cd6877449e6a1415589d22ff"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b48616a09d7df9dbae2f45a0256eee7b794b903ddc6d8657a9948669b345f220"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d4427b4a136e3b7f85516c76dd2e0756c22eec4026afb76ca1397152b0ca8145"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:26b0358e8933990502f4513c991c9935b6c06af01787a36d133b7c39b1df37fa"}, + {file = "pyzmq-25.0.2-cp36-cp36m-win32.whl", hash = "sha256:c8fedc3ccd62c6b77dfe6f43802057a803a411ee96f14e946f4a76ec4ed0e117"}, + {file = "pyzmq-25.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2da6813b7995b6b1d1307329c73d3e3be2fd2d78e19acfc4eff2e27262732388"}, + {file = "pyzmq-25.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a35960c8b2f63e4ef67fd6731851030df68e4b617a6715dd11b4b10312d19fef"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2a0b880ab40aca5a878933376cb6c1ec483fba72f7f34e015c0f675c90b20"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:85762712b74c7bd18e340c3639d1bf2f23735a998d63f46bb6584d904b5e401d"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:64812f29d6eee565e129ca14b0c785744bfff679a4727137484101b34602d1a7"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:510d8e55b3a7cd13f8d3e9121edf0a8730b87d925d25298bace29a7e7bc82810"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b164cc3c8acb3d102e311f2eb6f3c305865ecb377e56adc015cb51f721f1dda6"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:28fdb9224a258134784a9cf009b59265a9dde79582fb750d4e88a6bcbc6fa3dc"}, + {file = "pyzmq-25.0.2-cp37-cp37m-win32.whl", hash = "sha256:dd771a440effa1c36d3523bc6ba4e54ff5d2e54b4adcc1e060d8f3ca3721d228"}, + {file = "pyzmq-25.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9bdc40efb679b9dcc39c06d25629e55581e4c4f7870a5e88db4f1c51ce25e20d"}, + {file = "pyzmq-25.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:1f82906a2d8e4ee310f30487b165e7cc8ed09c009e4502da67178b03083c4ce0"}, + {file = "pyzmq-25.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:21ec0bf4831988af43c8d66ba3ccd81af2c5e793e1bf6790eb2d50e27b3c570a"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbce982a17c88d2312ec2cf7673985d444f1beaac6e8189424e0a0e0448dbb3"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e1d2f2d86fc75ed7f8845a992c5f6f1ab5db99747fb0d78b5e4046d041164d2"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e92ff20ad5d13266bc999a29ed29a3b5b101c21fdf4b2cf420c09db9fb690e"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edbbf06cc2719889470a8d2bf5072bb00f423e12de0eb9ffec946c2c9748e149"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77942243ff4d14d90c11b2afd8ee6c039b45a0be4e53fb6fa7f5e4fd0b59da39"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ab046e9cb902d1f62c9cc0eca055b1d11108bdc271caf7c2171487298f229b56"}, + {file = "pyzmq-25.0.2-cp38-cp38-win32.whl", hash = "sha256:ad761cfbe477236802a7ab2c080d268c95e784fe30cafa7e055aacd1ca877eb0"}, + {file = "pyzmq-25.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8560756318ec7c4c49d2c341012167e704b5a46d9034905853c3d1ade4f55bee"}, + {file = "pyzmq-25.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:ab2c056ac503f25a63f6c8c6771373e2a711b98b304614151dfb552d3d6c81f6"}, + {file = "pyzmq-25.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cca8524b61c0eaaa3505382dc9b9a3bc8165f1d6c010fdd1452c224225a26689"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb9f7eae02d3ac42fbedad30006b7407c984a0eb4189a1322241a20944d61e5"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5eaeae038c68748082137d6896d5c4db7927e9349237ded08ee1bbd94f7361c9"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a31992a8f8d51663ebf79df0df6a04ffb905063083d682d4380ab8d2c67257c"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6a979e59d2184a0c8f2ede4b0810cbdd86b64d99d9cc8a023929e40dce7c86cc"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1f124cb73f1aa6654d31b183810febc8505fd0c597afa127c4f40076be4574e0"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:65c19a63b4a83ae45d62178b70223adeee5f12f3032726b897431b6553aa25af"}, + {file = "pyzmq-25.0.2-cp39-cp39-win32.whl", hash = "sha256:83d822e8687621bed87404afc1c03d83fa2ce39733d54c2fd52d8829edb8a7ff"}, + {file = "pyzmq-25.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:24683285cc6b7bf18ad37d75b9db0e0fefe58404e7001f1d82bf9e721806daa7"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a4b4261eb8f9ed71f63b9eb0198dd7c934aa3b3972dac586d0ef502ba9ab08b"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:62ec8d979f56c0053a92b2b6a10ff54b9ec8a4f187db2b6ec31ee3dd6d3ca6e2"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:affec1470351178e892121b3414c8ef7803269f207bf9bef85f9a6dd11cde264"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffc71111433bd6ec8607a37b9211f4ef42e3d3b271c6d76c813669834764b248"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6fadc60970714d86eff27821f8fb01f8328dd36bebd496b0564a500fe4a9e354"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:269968f2a76c0513490aeb3ba0dc3c77b7c7a11daa894f9d1da88d4a0db09835"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f7c8b8368e84381ae7c57f1f5283b029c888504aaf4949c32e6e6fb256ec9bf0"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25e6873a70ad5aa31e4a7c41e5e8c709296edef4a92313e1cd5fc87bbd1874e2"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b733076ff46e7db5504c5e7284f04a9852c63214c74688bdb6135808531755a3"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a6f6ae12478fdc26a6d5fdb21f806b08fa5403cd02fd312e4cb5f72df078f96f"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:67da1c213fbd208906ab3470cfff1ee0048838365135a9bddc7b40b11e6d6c89"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531e36d9fcd66f18de27434a25b51d137eb546931033f392e85674c7a7cea853"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34a6fddd159ff38aa9497b2e342a559f142ab365576284bc8f77cb3ead1f79c5"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b491998ef886662c1f3d49ea2198055a9a536ddf7430b051b21054f2a5831800"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5d496815074e3e3d183fe2c7fcea2109ad67b74084c254481f87b64e04e9a471"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:56a94ab1d12af982b55ca96c6853db6ac85505e820d9458ac76364c1998972f4"}, + {file = "pyzmq-25.0.2.tar.gz", hash = "sha256:6b8c1bbb70e868dc88801aa532cae6bd4e3b5233784692b786f17ad2962e5149"}, ] [package.dependencies] @@ -3516,14 +3566,14 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "qtconsole" -version = "5.4.0" +version = "5.4.3" description = "Jupyter Qt console" category = "dev" optional = false python-versions = ">= 3.7" files = [ - {file = "qtconsole-5.4.0-py3-none-any.whl", hash = "sha256:be13560c19bdb3b54ed9741a915aa701a68d424519e8341ac479a91209e694b2"}, - {file = "qtconsole-5.4.0.tar.gz", hash = "sha256:57748ea2fd26320a0b77adba20131cfbb13818c7c96d83fafcb110ff55f58b35"}, + {file = "qtconsole-5.4.3-py3-none-any.whl", hash = "sha256:35fd6e87b1f6d1fd41801b07e69339f8982e76afd4fa8ef35595bc6036717189"}, + {file = "qtconsole-5.4.3.tar.gz", hash = "sha256:5e4082a86a201796b2a5cfd4298352d22b158b51b57736531824715fc2a979dd"}, ] [package.dependencies] @@ -3531,6 +3581,7 @@ ipykernel = ">=4.1" ipython-genutils = "*" jupyter-client = ">=4.1" jupyter-core = "*" +packaging = "*" pygments = "*" pyzmq = ">=17.1" qtpy = ">=2.0.1" @@ -3542,14 +3593,14 @@ test = ["flaky", "pytest", "pytest-qt"] [[package]] name = "qtpy" -version = "2.3.0" +version = "2.3.1" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, - {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, + {file = "QtPy-2.3.1-py3-none-any.whl", hash = "sha256:5193d20e0b16e4d9d3bc2c642d04d9f4e2c892590bd1b9c92bfe38a95d5a2e12"}, + {file = "QtPy-2.3.1.tar.gz", hash = "sha256:a8c74982d6d172ce124d80cafd39653df78989683f760f2281ba91a6e7b9de8b"}, ] [package.dependencies] @@ -3560,21 +3611,21 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] [[package]] name = "requests" -version = "2.28.2" +version = "2.30.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, + {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, + {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -3582,21 +3633,21 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rope" -version = "1.7.0" +version = "1.8.0" description = "a python refactoring library..." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "rope-1.7.0-py3-none-any.whl", hash = "sha256:893dd80ba7077fc9f6f42b0a849372076b70f1d6e405b9f0cc52781ffa0e6890"}, - {file = "rope-1.7.0.tar.gz", hash = "sha256:ba39581d0f8dee4ae8b5b5e82e35d03cebad965ccb127b7eaab9755cdc85e85a"}, + {file = "rope-1.8.0-py3-none-any.whl", hash = "sha256:0767424ed40ce237dcf1c1f5088054fef960e5b19f4a0850783a259a3600d7bd"}, + {file = "rope-1.8.0.tar.gz", hash = "sha256:3de1d1f1cf2412540c6a150067fe25298175e7c2b72455b6ca8395f61678da82"}, ] [package.dependencies] pytoolconfig = {version = ">=1.2.2", extras = ["global"]} [package.extras] -dev = ["build (>=0.7.0)", "pre-commit (>=2.20.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"] +dev = ["build (>=0.7.0)", "pip-tools (>=6.12.1)", "pre-commit (>=2.20.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)", "toml (>=0.10.2)"] doc = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] [[package]] @@ -3716,14 +3767,14 @@ stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] [[package]] name = "send2trash" -version = "1.8.0" -description = "Send file to trash natively under Mac OS X, Windows and Linux." +version = "1.8.2" +description = "Send file to trash natively under Mac OS X, Windows and Linux" category = "dev" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ - {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, - {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, + {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, + {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, ] [package.extras] @@ -3733,14 +3784,14 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "67.2.0" +version = "67.7.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, - {file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, + {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, + {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, ] [package.extras] @@ -3748,27 +3799,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "setuptools-scm" -version = "6.4.2" -description = "the blessed package to manage your versions by scm tags" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, - {file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"}, -] - -[package.dependencies] -packaging = ">=20.0" -setuptools = "*" -tomli = ">=1.0.0" - -[package.extras] -test = ["pytest (>=6.2)", "virtualenv (>20)"] -toml = ["setuptools (>=42)"] - [[package]] name = "six" version = "1.16.0" @@ -3807,14 +3837,14 @@ files = [ [[package]] name = "soupsieve" -version = "2.3.2.post1" +version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] [[package]] @@ -3942,18 +3972,18 @@ test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jquery" -version = "2.0.0" +version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" category = "main" optional = false python-versions = ">=2.7" files = [ - {file = "sphinxcontrib-jquery-2.0.0.tar.gz", hash = "sha256:8fb65f6dba84bf7bcd1aea1f02ab3955ac34611d838bcc95d4983b805b234daa"}, - {file = "sphinxcontrib_jquery-2.0.0-py3-none-any.whl", hash = "sha256:ed47fa425c338ffebe3c37e1cdb56e30eb806116b85f01055b158c7057fdb995"}, + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, ] [package.dependencies] -setuptools = "*" +Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-jsmath" @@ -4004,53 +4034,53 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.2" +version = "2.0.12" description = "Database Abstraction Library" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8efcdfe08efd12ee91f5e858adb34a0a73071a95c09c47303a656a9bac3b93b"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba1ec69fa7abb23c2bc9fcd10b3386ea1f62f9d558b7f3a749988374e6cb6ee5"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293c51d60da2ad52fb88d3f2357ff7ac3c156c5cb81feadd8258d45ebeea0a65"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80ddc3bf6088ce83437cdfb2e32099b82a2819c409c85b782a84aa1143f8eda"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dd3595576c6cdfd91eb1aaa18f542e9db87b5a5f3552573892d37d91102fd951"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0666025f44119bd18e1c5ceb536422356dc176d3230a13979ab2a7dd5d24b899"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:9e28dc39dd739f48d38486811f5a703673ed11e5968402fff835e60cbf379833"}, - {file = "SQLAlchemy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8a3bd94c7d04fed688ba333bfb5a0106800112b9f623aed1e5b0b6466e8c5c66"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7892245a79d612e163d2ce72b1e77c863840f7453e9469f6dd3e6b9ded67739"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2acafb0ef17d591a13621581b5d1abddb3dd25f601f368173135922915f56cd"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd9326886d424616f6c1b90d99b58c8a99c51f4515c8f8550830f9d4ac04ef3"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1f661cc378d61f495af49d8af83f779bb802046394e63ab1269b4180fceba0"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:55a72010983b655a638ea318b4efa30b04b001c158298ceb47fbf5da296092b0"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f91a4d0815fa9d236380a27762f7bd1304631605b8e2aa75b6b7d03343fd8fe8"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:27e8a5359c6227f3d665b1556169de840b375cf055c800a9bc27ed33f3c51e1b"}, - {file = "SQLAlchemy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:bfac4cb8585d7c5b06567c754e999410eb12248f8ce2474dc2d9bc53be1fba0c"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ebf6fe4cfd3b79f582b750b7ba69ba779d3fc7498818f8251dc6d38f5d5e206f"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92dc029273615ffbb40fa0e1cc8190618f1603ad98b1ad59d57078f1feb969b9"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b79ab1b7f5c39050daa03c100115efa2d8f996f14bc33cc9535cb60ca8357c4"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fc486297fe7f1465a2baac371d2153643a34ea1af803b68443bc359cfdc2bbcf"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a9d46844f4c88dc6cbfcabc6985beebd34661ef3b0e75c216a6428939fb3669"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3381fa42e2b388abf448800cf550bbf174eff41f6c57b362077db142417d2922"}, - {file = "SQLAlchemy-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8f3e9b71425af186fa51ab0d6172ef81809eb0e9f89b035612a913f8a9b44e4b"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:004459a78d376ab9c79ac1b613e0f0d44e88a105e4232357d0c7fa4c7b7e3a49"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:38ab55db6ce10c1ab2424d1dfed0add6e6afdeb1c65cec776ac6a01c1fb2f90a"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d34374993910d4520681b7b95ea60e05aa18a8ef7d36ffa94bde87730f6426d"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c556e01b0bbaea85ce030af4347e47fcb695c3254cfd480099ec1ed6ea609"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:061c7be6b7042f298187e39528c85b4de233361412c88de9fba62a3ee727eb71"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:301f5fb0718e000d47db6ebcee64de91fa94865d7b59872258ba36e4eafbfe8f"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-win32.whl", hash = "sha256:35765bf1ff823d05cf9c4eb18ba2db2290b2a2e2196d1a1d06d714ea55c1b997"}, - {file = "SQLAlchemy-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a9205379388e57c486244e285d5d5c047ef9241bd4238e9469a4873b75d1e4a"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6282a712dbcfd86e3fee2e4c459c7337141f774450f60809fd0f0edebd07f37a"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43482b8f0bed711b3769037e86c45619370bf529aee605975586fdd80ee770a0"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fec1aebc9256b02a59e8a69d4b7aa2299e04e7b51bb4fd11daaba5df1ad592c7"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8baa4e421136a582f5d9d262ce568ce3eb413d3455650c34d674e5a9f092aa"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:90550ff7a96143579472a93aff6dae0999316528ce0f3165083f93d036e313bf"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edf1a0cfd9979a805ad0d725e436d6460a5b44afc7de0ffae3321a9d2afeff54"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:abeb371ff47633c7d16b1e1e56e3d41a9b0ab34463c1726f22e1b68401d28cac"}, - {file = "SQLAlchemy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:b0323d65b4cd9408261026495a650566d62d056adeb848d1f1f8be0da38a1e0a"}, - {file = "SQLAlchemy-2.0.2-py3-none-any.whl", hash = "sha256:d561e44c0b480b7e74fa002bea949c9f5aff555724f4d10883391cbd8790c81b"}, - {file = "SQLAlchemy-2.0.2.tar.gz", hash = "sha256:e4d3cf5352f724237e27463de4bf19888235342af2efa37ef1cd391d8b702a16"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10f1ff0ebe21d2cea89ead231ba3ecf75678463ab85f19ce2ce91207620737f3"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:978bee4ecbcdadf087220618409fb9be9509458df479528b70308f0599c7c519"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53b2c8adbcbb59732fb21a024aaa261983655845d86e3fc26a5676cec0ebaa09"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91f4b1bdc987ef85fe3a0ce5d26ac72ff8f60207b08272aa2a65494836391d69"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dfd6385b662aea83e63dd4db5fe116eb11914022deb1745f0b57fa8470c18ffe"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5e9d390727c11b9a7e583bf6770de36895c0936bddb98ae93ae99282e6428d5f"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-win32.whl", hash = "sha256:a4709457f1c317e347051498b91fa2b86c4bcdebf93c84e6d121a4fc8a397307"}, + {file = "SQLAlchemy-2.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:f0843132168b44ca33c5e5a2046c954775dde8c580ce27f5cf2e134d0d9919e4"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:32762dba51b663609757f861584a722093487f53737e76474cc6e190904dc31b"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d709f43caee115b03b707b8cbbcb8b303045dd7cdc825b6d29857d71f3425ae"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe98e9d26778d7711ceee2c671741b4f54c74677668481d733d6f70747d7690"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3101252f3de9a18561c1fb0a68b1ee465485990aba458d4510f214bd5a582c"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b1fa0ffc378a7061c452cb4a1f804fad1b3b8aa8d0552725531d27941b2e3ed"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c5268ec05c21e2ecf5bca09314bcaadfec01f02163088cd602db4379862958dd"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-win32.whl", hash = "sha256:77a06b0983faf9aa48ee6219d41ade39dee16ce90857cc181dbcf6918acd234d"}, + {file = "SQLAlchemy-2.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:a022c588c0f413f8cddf9fcc597dbf317efeac4186d8bff9aa7f3219258348b0"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6ceca432ce88ad12aab5b5896c343a1993c90b325d9193dcd055e73e18a0439"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5501c78b5ab917f0f0f75ce7f0018f683a0a76e95f30e6561bf61c9ff69d43"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67efd00ce7f428a446ce012673c03c63c5abb5dec3f33750087b8bdc173bf0"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1fac17c866111283cbcdb7024d646abb71fdd95f3ce975cf3710258bc55742fd"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f30c5608c64fc9c1fa9a16277eb4784f782362566fe40ff8d283358c8f2c5fe0"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-win32.whl", hash = "sha256:85b0efe1c71459ba435a6593f54a0e39334b16ba383e8010fdb9d0127ca51ba8"}, + {file = "SQLAlchemy-2.0.12-cp37-cp37m-win_amd64.whl", hash = "sha256:b76c2fde827522e21922418325c1b95c2d795cdecfb4bc261e4d37965199ee7f"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aec5fb36b53125554ecc2285526eb5cc31b21f6cb059993c1c5ca831959de052"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad525b9dd17b478a2ed8580d7f2bc46b0f5889153c6b1c099729583e395b4b9"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9796d5c13b2b7f05084d0ce52528cf919f9bde9e0f10672a6393a4490415695"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e1d50592cb24d1947c374c666add65ded7c181ec98a89ed17abbe9b8b2e2ff4"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bf83700faa9642388fbd3167db3f6cbb2e88cc8367b8c22204f3f408ee782d25"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:297b752d4f30350b64175bbbd57dc94c061a35f5d1dba088d0a367dbbebabc94"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-win32.whl", hash = "sha256:369f6564e68a9c60f0b9dde121def491e651a4ba8dcdd652a93f1cd5977cd85c"}, + {file = "SQLAlchemy-2.0.12-cp38-cp38-win_amd64.whl", hash = "sha256:7eb25b981cbc9e7df9f56ad7ec4c6d77323090ca4b7147fcdc09d66535377759"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6ebadefc4331dda83c22519e1ea1e61104df6eb38abbb80ab91b0a8527a5c19"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3745dee26a7ee012598577ad3b8f6e6cd50a49b2afa0cde9db668da6bf2c2319"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09205893a84b6bedae0453d3f384f5d2a6499b6e45ad977549894cdcd85d8f1c"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aad66215a3817a7a1d535769773333250de2653c89b53f7e2d42b677d398027"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e495ad05a13171fbb5d72fe5993469c8bceac42bcf6b8f9f117a518ee7fbc353"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03206576ca53f55b9de6e890273e498f4b2e6e687a9db9859bdcd21df5a63e53"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-win32.whl", hash = "sha256:87b2c2d13c3d1384859b60eabb3139e169ce68ada1d2963dbd0c7af797f16efe"}, + {file = "SQLAlchemy-2.0.12-cp39-cp39-win_amd64.whl", hash = "sha256:3c053c3f4c4e45d4c8b27977647566c140d6de3f61a4e2acb92ea24cf9911c7f"}, + {file = "SQLAlchemy-2.0.12-py3-none-any.whl", hash = "sha256:e752c34f7a2057ebe82c856698b9f277c633d4aad006bddf7af74598567c8931"}, + {file = "SQLAlchemy-2.0.12.tar.gz", hash = "sha256:bddfc5bd1dee5db0fddc9dab26f800c283f3243e7281bbf107200fed30125f9c"}, ] [package.dependencies] @@ -4122,7 +4152,7 @@ packaging = ">=21.3" pandas = ">=0.25" patsy = ">=0.5.2" scipy = [ - {version = ">=1.3", markers = "(python_version > \"3.9\" or platform_system != \"Windows\" or platform_machine != \"x86\") and python_version < \"3.12\""}, + {version = ">=1.3", markers = "python_version > \"3.9\" and python_version < \"3.12\" or platform_system != \"Windows\" and python_version < \"3.12\" or platform_machine != \"x86\" and python_version < \"3.12\""}, {version = ">=1.3,<1.8", markers = "python_version == \"3.7\""}, {version = ">=1.3,<1.9", markers = "python_version == \"3.8\" and platform_system == \"Windows\" and platform_machine == \"x86\" or python_version == \"3.9\" and platform_system == \"Windows\" and platform_machine == \"x86\""}, ] @@ -4134,14 +4164,14 @@ docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "n [[package]] name = "strenum" -version = "0.4.9" +version = "0.4.10" description = "An Enum that inherits from str." category = "main" optional = false python-versions = "*" files = [ - {file = "StrEnum-0.4.9-py3-none-any.whl", hash = "sha256:c3df653030837b8077b2eb929738283481f01b07c7ef73ab4db368449830cc46"}, - {file = "StrEnum-0.4.9.tar.gz", hash = "sha256:1b15ccff9eb0e22e77b8f874366ea752754280adc9144e6e388705d14cc350e7"}, + {file = "StrEnum-0.4.10-py3-none-any.whl", hash = "sha256:aebf04bba8e5af435937c452d69a86798b6f8d5ca5f20ba18561dbfad571ccdd"}, + {file = "StrEnum-0.4.10.tar.gz", hash = "sha256:898cc0ebb5054ee07400341ac1d75fdfee489d76d6df3fbc1c2eaf95971e3916"}, ] [package.extras] @@ -4151,14 +4181,14 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] [[package]] name = "tenacity" -version = "8.2.0" +version = "8.2.2" description = "Retry code until it succeeds" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "tenacity-8.2.0-py3-none-any.whl", hash = "sha256:b723061a78ed0f4524190eae321d3d84100227d51c5677035b6615d91895e0d6"}, - {file = "tenacity-8.2.0.tar.gz", hash = "sha256:a43bcd8910406e0884ca0db3db7bed581f389c1d05165e992a1ddabfc81df05e"}, + {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, + {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, ] [package.extras] @@ -4232,7 +4262,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4332,14 +4362,14 @@ files = [ [[package]] name = "tqdm" -version = "4.64.1" +version = "4.65.0" description = "Fast, Extensible Progress Meter" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.7" files = [ - {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, - {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, + {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, + {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, ] [package.dependencies] @@ -4403,32 +4433,33 @@ files = [ [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] name = "urllib3" -version = "1.26.14" +version = "2.0.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, + {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" @@ -4496,107 +4527,118 @@ test = ["websockets"] [[package]] name = "widgetsnbextension" -version = "4.0.5" +version = "4.0.7" description = "Jupyter interactive widgets for Jupyter Notebook" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "widgetsnbextension-4.0.5-py3-none-any.whl", hash = "sha256:eaaaf434fb9b08bd197b2a14ffe45ddb5ac3897593d43c69287091e5f3147bf7"}, - {file = "widgetsnbextension-4.0.5.tar.gz", hash = "sha256:003f716d930d385be3fd9de42dd9bf008e30053f73bddde235d14fbeaeff19af"}, + {file = "widgetsnbextension-4.0.7-py3-none-any.whl", hash = "sha256:be3228a73bbab189a16be2d4a3cd89ecbd4e31948bfdc64edac17dcdee3cd99c"}, + {file = "widgetsnbextension-4.0.7.tar.gz", hash = "sha256:ea67c17a7cd4ae358f8f46c3b304c40698bc0423732e3f273321ee141232c8be"}, ] [[package]] name = "wrapt" -version = "1.14.1" +version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] [[package]] name = "zipp" -version = "3.12.1" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.12.1-py3-none-any.whl", hash = "sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3"}, - {file = "zipp-3.12.1.tar.gz", hash = "sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02"}, + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = ">=3.7, <3.10" -content-hash = "1b67e8140f9b46b8a368fd7da92fca532c79d765cfbb567ef51f9ee3d3366797" +content-hash = "012c36aec17f0ed8f8864c6027f8e6f770cefe5ee9d0679e67fdaa8cdfd530ea" diff --git a/pyproject.toml b/pyproject.toml index f02f14e13..af3cc90ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ packages = [ [tool.poetry.dependencies] python = ">=3.7, <3.10" +poetry-core = "1.6.0" lightfm = "*" lightautoml = ">=0.3.1, <0.3.7" numpy = ">=1.20.0" @@ -46,6 +47,11 @@ seaborn = "*" pyarrow = "*" implicit = ">=0.5" pytorch-ranger = "^0.1.1" +nmslib = "*" +hnswlib = "*" +cached-property = "*" +# extra packages +pygloo-rec = { version = "*", optional = true } [tool.poetry.dev-dependencies] # dev only @@ -77,6 +83,9 @@ virtualenv = "*" data-science-types = "*" pyspark-stubs = "*" +[tool.poetry.extras] +distributed-lightfm = ["pygloo-rec"] + [tool.black] line-length = 79 diff --git a/replay/ann/__init__.py b/replay/ann/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/replay/ann/ann_mixin.py b/replay/ann/ann_mixin.py new file mode 100644 index 000000000..ac06baaea --- /dev/null +++ b/replay/ann/ann_mixin.py @@ -0,0 +1,202 @@ +import importlib +import logging +from abc import abstractmethod +from typing import Optional, Dict, Any + +from pyspark.sql import DataFrame +from pyspark.sql import functions as sf + +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.ann.index_stores.spark_files_index_store import ( + SparkFilesIndexStore, +) +from replay.models.base_rec import BaseRecommender + +logger = logging.getLogger("replay") + + +class ANNMixin(BaseRecommender): + """ + This class overrides the `_fit_wrap` and `_inner_predict_wrap` methods of the base class, + adding an index construction in the `_fit_wrap` step + and an index inference in the `_inner_predict_wrap` step. + """ + + index_builder: Optional[IndexBuilder] = None + + @property + def _use_ann(self) -> bool: + """ + Property that determines whether the ANN (index) is used. + If `True`, then the index will be built (at the `fit` stage) + and index will be inferred (at the `predict` stage). + """ + return self.index_builder is not None + + @abstractmethod + def _get_vectors_to_build_ann(self, log: DataFrame) -> DataFrame: + """Implementations of this method must return a dataframe with item vectors. + Item vectors from this method are used to build the index. + + Args: + log: DataFrame with interactions + + Returns: DataFrame[item_idx int, vector array] or DataFrame[vector array]. + Column names in dataframe can be anything. + """ + + @abstractmethod + def _get_ann_build_params(self, log: DataFrame) -> Dict[str, Any]: + """Implementation of this method must return dictionary + with arguments for `_build_ann_index` method. + + Args: + log: DataFrame with interactions + + Returns: Dictionary with arguments to build index. For example: { + "id_col": "item_idx", + "features_col": "item_factors", + ... + } + + """ + + def _fit_wrap( + self, + log: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + ) -> None: + """Wrapper extends `_fit_wrap`, adds construction of ANN index by flag. + + Args: + log: historical log of interactions + ``[user_idx, item_idx, timestamp, relevance]`` + user_features: user features + ``[user_idx, timestamp]`` + feature columns + item_features: item features + ``[item_idx, timestamp]`` + feature columns + + """ + super()._fit_wrap(log, user_features, item_features) + + if self._use_ann: + vectors = self._get_vectors_to_build_ann(log) + ann_params = self._get_ann_build_params(log) + self.index_builder.build_index(vectors, **ann_params) + + @abstractmethod + def _get_vectors_to_infer_ann_inner( + self, log: DataFrame, users: DataFrame + ) -> DataFrame: + """Implementations of this method must return a dataframe with user vectors. + User vectors from this method are used to infer the index. + + Args: + log: DataFrame with interactions + users: DataFrame with users + + Returns: DataFrame[user_idx int, vector array] or DataFrame[vector array]. + Vector column name in dataframe can be anything. + """ + + def _get_vectors_to_infer_ann( + self, log: DataFrame, users: DataFrame, filter_seen_items: bool + ) -> DataFrame: + """This method wraps `_get_vectors_to_infer_ann_inner` + and adds seen items to dataframe with user vectors by flag. + + Args: + log: DataFrame with interactions + users: DataFrame with users + filter_seen_items: flag to remove seen items from recommendations based on ``log``. + + Returns: + + """ + users = self._get_vectors_to_infer_ann_inner(log, users) + + # here we add `seen_item_idxs` to filter the viewed items in UDFs (see infer_index_udf) + if filter_seen_items: + user_to_max_items = log.groupBy("user_idx").agg( + sf.count("item_idx").alias("num_items"), + sf.collect_set("item_idx").alias("seen_item_idxs"), + ) + users = users.join(user_to_max_items, on="user_idx") + + return users + + @abstractmethod + def _get_ann_infer_params(self) -> Dict[str, Any]: + """Implementation of this method must return dictionary + with arguments for `_infer_ann_index` method. + + Returns: Dictionary with arguments to infer index. For example: { + "features_col": "user_vector", + ... + } + + """ + + def _inner_predict_wrap( # pylint: disable=too-many-arguments + self, + log: DataFrame, + k: int, + users: DataFrame, + items: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + filter_seen_items: bool = True, + ) -> DataFrame: + """Override base `_inner_predict_wrap` and adds ANN inference by condition""" + if self._use_ann: + vectors = self._get_vectors_to_infer_ann( + log, users, filter_seen_items + ) + ann_params = self._get_ann_infer_params() + inferer = self.index_builder.produce_inferer(filter_seen_items) + return inferer.infer(vectors, ann_params["features_col"], k) + else: + return self._predict( + log, + k, + users, + items, + user_features, + item_features, + filter_seen_items, + ) + + def _filter_seen( + self, recs: DataFrame, log: DataFrame, k: int, users: DataFrame + ): + """ + Overridden _filter_seen method from base class. + Filtering is not needed for ann methods, because the data is already filtered in udf. + """ + if self._use_ann: + return recs + + return super()._filter_seen(recs, log, k, users) + + def _save_index(self, path): + self.index_builder.index_store.dump_index(path) + + def _load_index(self, path: str): + self.index_builder.index_store = SparkFilesIndexStore() + self.index_builder.index_store.load_from_path(path) + + def init_builder_from_dict(self, init_meta: dict): + """Inits an index builder instance from a dict with init meta.""" + + # index param entity instance initialization + module = importlib.import_module(init_meta["index_param"]["module"]) + class_ = getattr(module, init_meta["index_param"]["class"]) + index_params = class_(**init_meta["index_param"]["init_args"]) + + # index builder instance initialization + module = importlib.import_module(init_meta["builder"]["module"]) + class_ = getattr(module, init_meta["builder"]["class"]) + index_builder = class_(index_params=index_params, index_store=None) + + self.index_builder = index_builder diff --git a/replay/ann/entities/__init__.py b/replay/ann/entities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/replay/ann/entities/base_hnsw_param.py b/replay/ann/entities/base_hnsw_param.py new file mode 100644 index 000000000..2090596b6 --- /dev/null +++ b/replay/ann/entities/base_hnsw_param.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class BaseHnswParam: + """ + Base hnsw params. + """ + + space: str + m: int = 200 # pylint: disable=invalid-name + ef_c: int = 20000 + post: int = 0 + ef_s: Optional[int] = None + + def init_meta_as_dict(self) -> dict: + """ + Returns meta-information for class instance initialization. Used to save the entity to disk. + :return: dictionary with init meta. + """ + return { + "module": type(self).__module__, + "class": type(self).__name__, + "init_args": { + "space": self.space, + "m": self.m, + "ef_c": self.ef_c, + "post": self.post, + "ef_s": self.ef_s, + }, + } diff --git a/replay/ann/entities/hnswlib_param.py b/replay/ann/entities/hnswlib_param.py new file mode 100644 index 000000000..331186955 --- /dev/null +++ b/replay/ann/entities/hnswlib_param.py @@ -0,0 +1,66 @@ +from dataclasses import dataclass, field +from typing_extensions import Literal + +from replay.ann.entities.base_hnsw_param import BaseHnswParam + + +@dataclass +class HnswlibParam(BaseHnswParam): + """ + Parameters for hnswlib methods. + + For example, + + >>> HnswlibParam(space="ip",\ + m=100,\ + ef_c=200,\ + post=0,\ + ef_s=2000,\ + ) + HnswlibParam(space='ip', m=100, ef_c=200, post=0, ef_s=2000, dim=None, max_elements=None) + + or + + >>> HnswlibParam(space="ip",\ + m=100,\ + ef_c=200,\ + post=0,\ + ef_s=2000,\ + ) + HnswlibParam(space='ip', m=100, ef_c=200, post=0, ef_s=2000, dim=None, max_elements=None) + + The "space" parameter described on the page https://github.com/nmslib/hnswlib/blob/master/README.md#supported-distances + Parameters "m", "ef_s" and "ef_c" are described at https://github.com/nmslib/hnswlib/blob/master/ALGO_PARAMS.md + + The reasonable range of values for `m` parameter is 5-100, + for `ef_c` and `ef_s` is 100-2000. + Increasing these values improves the prediction quality + but increases index_time and inference_time too. + + We recommend using these settings: + + - m=16, ef_c=200 and ef_s=200 for simple datasets like MovieLens. + - m=50, ef_c=1000 and ef_s=1000 for average quality with an average prediction time. + - m=75, ef_c=2000 and ef_s=2000 for the highest quality with a long prediction time. + + note: choosing these parameters depends on the dataset + and quality/time tradeoff. + + note: while reducing parameter values the highest range metrics + like Metric@1000 suffer first. + + note: even in a case with a long training time, + profit from ann could be obtained while inference will be used multiple times. + """ + + space: Literal["l2", "ip", "cosine"] = "ip" + # Dimension of vectors in index + dim: int = field(default=None, init=False) + # Max number of elements that will be stored in the index + max_elements: int = field(default=None, init=False) + + # def init_args_as_dict(self): + # # union dicts + # return dict( + # super().init_args_as_dict()["init_args"], **{"space": self.space} + # ) diff --git a/replay/ann/entities/nmslib_hnsw_param.py b/replay/ann/entities/nmslib_hnsw_param.py new file mode 100644 index 000000000..9e852dbbb --- /dev/null +++ b/replay/ann/entities/nmslib_hnsw_param.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass, field +from typing import ClassVar, Optional +from typing_extensions import Literal + +from replay.ann.entities.base_hnsw_param import BaseHnswParam + + +@dataclass +class NmslibHnswParam(BaseHnswParam): + """ + Parameters for nmslib-hnsw methods. + + For example, + + >>> NmslibHnswParam(space='negdotprod_sparse',\ + m=10,\ + ef_s=200,\ + ef_c=200,\ + post=0,\ + ) + NmslibHnswParam(space='negdotprod_sparse', m=10, ef_c=200, post=0, ef_s=200, items_count=None) + + or + + >>> NmslibHnswParam(space='negdotprod_sparse',\ + m=10,\ + ef_s=200,\ + ef_c=200,\ + post=0,\ + ) + NmslibHnswParam(space='negdotprod_sparse', m=10, ef_c=200, post=0, ef_s=200, items_count=None) + + The reasonable range of values for `m` parameter is 5-100, + for `ef_c` and `ef_s` is 100-2000. + Increasing these values improves the prediction quality + but increases index_time and inference_time too. + + We recommend using these settings: + + - m=16, ef_c=200 and ef_s=200 for simple datasets like MovieLens. + - m=50, ef_c=1000 and ef_s=1000 for average quality with an average prediction time. + - m=75, ef_c=2000 and ef_s=2000 for the highest quality with a long prediction time. + + note: choosing these parameters depends on the dataset + and quality/time tradeoff. + + note: while reducing parameter values the highest range metrics + like Metric@1000 suffer first. + + note: even in a case with a long training time, + profit from ann could be obtained while inference will be used multiple times. + + For more details see https://github.com/nmslib/nmslib/blob/master/manual/methods.md. + """ + + space: Literal[ + "cosinesimil_sparse", + "cosinesimil_sparse_fast", + "negdotprod_sparse", + "negdotprod_sparse_fast", + "angulardist_sparse", + "angulardist_sparse_fast", + ] = "negdotprod_sparse_fast" + items_count: Optional[int] = field(default=None, init=False) + + method: ClassVar[str] = "hnsw" + + # def init_args_as_dict(self): + # # union dicts + # return dict( + # super().init_args_as_dict()["init_args"], **{"space": self.space} + # ) diff --git a/replay/ann/index_builders/__init__.py b/replay/ann/index_builders/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/replay/ann/index_builders/base_index_builder.py b/replay/ann/index_builders/base_index_builder.py new file mode 100644 index 000000000..048b80f65 --- /dev/null +++ b/replay/ann/index_builders/base_index_builder.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from pyspark.sql import DataFrame + +from replay.ann.entities.base_hnsw_param import BaseHnswParam +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_stores.base_index_store import IndexStore + + +class IndexBuilder(ABC): + """Abstract base class for index builders. + Describes a common interface for index builders. And provides common methods for them. + """ + + def __init__(self, index_params: BaseHnswParam, index_store: IndexStore): + self.index_store = index_store + self.index_params = index_params + + @abstractmethod + def build_index( + self, + vectors: DataFrame, + features_col: str, + ids_col: Optional[str] = None, + ): + """ + Method that builds index and stores it using the `IndexStore` class. + + :param vectors: DataFrame with vectors to build index. + Schema: [{ids_col}: int, {features_col}: array] + or [{features_col}: array] + :param features_col: Name of column from `vectors` dataframe + that contains vectors to build index + :param ids_col: Name of column that contains identifiers of vectors. + None if `vectors` dataframe have no id column. + """ + + @abstractmethod + def produce_inferer(self, filter_seen_items: bool) -> IndexInferer: + """Method that produce `IndexInferer`.""" + + def init_meta_as_dict(self): + """ + Returns meta-information for class instance initialization. + Used to save the index builder to disk. + :return: dictionary with init meta. + """ + return { + "builder": { + "module": type(self).__module__, + "class": type(self).__name__, + }, + "index_param": self.index_params.init_meta_as_dict(), + } diff --git a/replay/ann/index_builders/driver_hnswlib_index_builder.py b/replay/ann/index_builders/driver_hnswlib_index_builder.py new file mode 100644 index 000000000..a2f600b4a --- /dev/null +++ b/replay/ann/index_builders/driver_hnswlib_index_builder.py @@ -0,0 +1,51 @@ +import logging +from typing import Optional + +import numpy as np +from pyspark.sql import DataFrame + +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.hnswlib_filter_index_inferer import ( + HnswlibFilterIndexInferer, +) +from replay.ann.index_inferers.hnswlib_index_inferer import HnswlibIndexInferer +from replay.ann.utils import create_hnswlib_index_instance + +logger = logging.getLogger("replay") + + +class DriverHnswlibIndexBuilder(IndexBuilder): + """ + Builder that builds hnswlib index on driver. + """ + + def produce_inferer(self, filter_seen_items: bool) -> IndexInferer: + if filter_seen_items: + return HnswlibFilterIndexInferer( + self.index_params, self.index_store + ) + else: + return HnswlibIndexInferer(self.index_params, self.index_store) + + def build_index( + self, + vectors: DataFrame, + features_col: str, + ids_col: Optional[str] = None, + ): + vectors = vectors.toPandas() + vectors_np = np.squeeze(vectors[features_col].values) + + index = create_hnswlib_index_instance(self.index_params, init=True) + + if ids_col: + index.add_items(np.stack(vectors_np), vectors[ids_col].values) + else: + index.add_items(np.stack(vectors_np)) + + self.index_store.save_to_store( + lambda path: index.save_index( # pylint: disable=unnecessary-lambda) + path + ) + ) diff --git a/replay/ann/index_builders/driver_nmslib_index_builder.py b/replay/ann/index_builders/driver_nmslib_index_builder.py new file mode 100644 index 000000000..aacc4462f --- /dev/null +++ b/replay/ann/index_builders/driver_nmslib_index_builder.py @@ -0,0 +1,65 @@ +import logging +from typing import Optional + +from pyspark.sql import DataFrame +from scipy.sparse import csr_matrix + +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.nmslib_filter_index_inferer import ( + NmslibFilterIndexInferer, +) +from replay.ann.index_inferers.nmslib_index_inferer import NmslibIndexInferer +from replay.ann.utils import create_nmslib_index_instance + +logger = logging.getLogger("replay") + + +class DriverNmslibIndexBuilder(IndexBuilder): + """ + Builder that builds nmslib hnsw index on driver. + """ + + def produce_inferer(self, filter_seen_items: bool) -> IndexInferer: + if filter_seen_items: + return NmslibFilterIndexInferer( + self.index_params, self.index_store + ) + else: + return NmslibIndexInferer(self.index_params, self.index_store) + + def build_index( + self, + vectors: DataFrame, + features_col: str, + ids_col: Optional[str] = None, + ): + index_params = { + "M": self.index_params.m, + "efConstruction": self.index_params.ef_c, + "post": self.index_params.post, + } + + vectors = vectors.toPandas() + + index = create_nmslib_index_instance(self.index_params) + + data = vectors["similarity"].values + row_ind = vectors["item_idx_two"].values + col_ind = vectors["item_idx_one"].values + + sim_matrix = csr_matrix( + (data, (row_ind, col_ind)), + shape=( + self.index_params.items_count, + self.index_params.items_count, + ), + ) + index.addDataPointBatch(data=sim_matrix) + index.createIndex(index_params) + + self.index_store.save_to_store( + lambda path: index.saveIndex( + path, save_data=True + ) # pylint: disable=unnecessary-lambda) + ) diff --git a/replay/ann/index_builders/executor_hnswlib_index_builder.py b/replay/ann/index_builders/executor_hnswlib_index_builder.py new file mode 100644 index 000000000..73ed086f3 --- /dev/null +++ b/replay/ann/index_builders/executor_hnswlib_index_builder.py @@ -0,0 +1,77 @@ +import logging +from typing import Optional, Iterator + +import numpy as np +import pandas as pd +from pyspark.sql import DataFrame + +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.hnswlib_filter_index_inferer import ( + HnswlibFilterIndexInferer, +) +from replay.ann.index_inferers.hnswlib_index_inferer import HnswlibIndexInferer +from replay.ann.utils import ( + create_hnswlib_index_instance, +) + +logger = logging.getLogger("replay") + + +class ExecutorHnswlibIndexBuilder(IndexBuilder): + """ + Builder that build hnswlib index on one executor. + """ + + def produce_inferer(self, filter_seen_items: bool) -> IndexInferer: + if filter_seen_items: + return HnswlibFilterIndexInferer( + self.index_params, self.index_store + ) + else: + return HnswlibIndexInferer(self.index_params, self.index_store) + + def build_index( + self, + vectors: DataFrame, + features_col: str, + ids_col: Optional[str] = None, + ): + # to execution in one executor + vectors = vectors.repartition(1) + + _index_store = self.index_store + _index_params = self.index_params + + def build_index_udf(iterator: Iterator[pd.DataFrame]): + """Builds index on executor and writes it to shared disk or hdfs. + + Args: + iterator: iterates on dataframes with vectors/features + + """ + index = create_hnswlib_index_instance(_index_params, init=True) + + # pdf is a pandas dataframe that contains ids and features (vectors) + for pdf in iterator: + vectors_np = np.squeeze(pdf[features_col].values) + if ids_col: + index.add_items(np.stack(vectors_np), pdf[ids_col].values) + else: + # ids will be from [0, ..., len(vectors_np)] + index.add_items(np.stack(vectors_np)) + + _index_store.save_to_store( + lambda path: index.save_index( # pylint: disable=unnecessary-lambda) + path + ) + ) + + yield pd.DataFrame(data={"_success": 1}, index=[0]) + + # Here we perform materialization (`.collect()`) to build the hnsw index. + cols = [ids_col, features_col] if ids_col else [features_col] + + vectors.select(*cols).mapInPandas( + build_index_udf, "_success int" + ).collect() diff --git a/replay/ann/index_builders/executor_nmslib_index_builder.py b/replay/ann/index_builders/executor_nmslib_index_builder.py new file mode 100644 index 000000000..fe38bb610 --- /dev/null +++ b/replay/ann/index_builders/executor_nmslib_index_builder.py @@ -0,0 +1,94 @@ +import logging +from typing import Optional, Iterator + +import pandas as pd +from pyspark.sql import DataFrame +from scipy.sparse import csr_matrix + +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.nmslib_filter_index_inferer import ( + NmslibFilterIndexInferer, +) +from replay.ann.index_inferers.nmslib_index_inferer import NmslibIndexInferer +from replay.ann.utils import ( + create_nmslib_index_instance, +) + +logger = logging.getLogger("replay") + + +class ExecutorNmslibIndexBuilder(IndexBuilder): + """ + Builder that build nmslib hnsw index on one executor. + """ + + def produce_inferer(self, filter_seen_items: bool) -> IndexInferer: + if filter_seen_items: + return NmslibFilterIndexInferer( + self.index_params, self.index_store + ) + else: + return NmslibIndexInferer(self.index_params, self.index_store) + + def build_index( + self, + vectors: DataFrame, + features_col: str, + ids_col: Optional[str] = None, + ): + index_params = { + "M": self.index_params.m, + "efConstruction": self.index_params.ef_c, + "post": self.index_params.post, + } + + # to execution in one executor + vectors = vectors.repartition(1) + + _index_store = self.index_store + _index_params = self.index_params + + def build_index_udf(iterator: Iterator[pd.DataFrame]): + """Builds index on executor and writes it to shared disk or hdfs. + + Args: + iterator: iterates on dataframes with vectors/features + + """ + index = create_nmslib_index_instance(_index_params) + + pdfs = [] + for pdf in iterator: + pdfs.append(pdf) + + pdf = pd.concat(pdfs, copy=False) + + # We collect all iterator values into one dataframe, + # because we cannot guarantee that `pdf` will contain rows + # with the same `item_idx_two`. + # And therefore we cannot call the `addDataPointBatch` iteratively. + data = pdf["similarity"].values + row_ind = pdf["item_idx_two"].values + col_ind = pdf["item_idx_one"].values + + sim_matrix_tmp = csr_matrix( + (data, (row_ind, col_ind)), + shape=( + self.index_params.items_count, + self.index_params.items_count, + ), + ) + index.addDataPointBatch(data=sim_matrix_tmp) + index.createIndex(index_params) + + _index_store.save_to_store( + lambda path: index.saveIndex(path, save_data=True) + ) # pylint: disable=unnecessary-lambda) + + yield pd.DataFrame(data={"_success": 1}, index=[0]) + + # Here we perform materialization (`.collect()`) to build the hnsw index. + vectors.select( + "similarity", "item_idx_one", "item_idx_two" + ).mapInPandas(build_index_udf, "_success int").collect() diff --git a/replay/ann/index_inferers/__init__.py b/replay/ann/index_inferers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/replay/ann/index_inferers/base_inferer.py b/replay/ann/index_inferers/base_inferer.py new file mode 100644 index 000000000..6b076a065 --- /dev/null +++ b/replay/ann/index_inferers/base_inferer.py @@ -0,0 +1,71 @@ +from abc import ABC, abstractmethod + +from pyspark.sql import DataFrame +from pyspark.sql import functions as sf + +from replay.ann.entities.base_hnsw_param import BaseHnswParam +from replay.ann.index_stores.base_index_store import IndexStore + + +# pylint: disable=too-few-public-methods +class IndexInferer(ABC): + """Abstract base class that describes a common interface for index inferers + and provides common methods for them.""" + + # All implementations use the same udf return type. + udf_return_type = "item_idx array, distance array" + + def __init__(self, index_params: BaseHnswParam, index_store: IndexStore): + self.index_params = index_params + self.index_store = index_store + + @abstractmethod + def infer( + self, vectors: DataFrame, features_col: str, k: int + ) -> DataFrame: + """Infers index""" + + @staticmethod + def _unpack_infer_struct(inference_result: DataFrame) -> DataFrame: + """Transforms input dataframe. + Unpacks and explodes arrays from `neighbours` struct. + + >>> + >> inference_result.printSchema() + root + |-- user_idx: integer (nullable = true) + |-- neighbours: struct (nullable = true) + | |-- item_idx: array (nullable = true) + | | |-- element: integer (containsNull = true) + | |-- distance: array (nullable = true) + | | |-- element: double (containsNull = true) + >> ANNMixin._unpack_infer_struct(inference_result).printSchema() + root + |-- user_idx: integer (nullable = true) + |-- item_idx: integer (nullable = true) + |-- relevance: double (nullable = true) + + Args: + inference_result: output of infer_index UDF + """ + res = inference_result.select( + "user_idx", + sf.explode( + sf.arrays_zip("neighbours.item_idx", "neighbours.distance") + ).alias("zip_exp"), + ) + + # Fix arrays_zip random behavior. + # It can return zip_exp.0 or zip_exp.item_idx in different machines. + fields = res.schema["zip_exp"].jsonValue()["type"]["fields"] + item_idx_field_name: str = fields[0]["name"] + distance_field_name: str = fields[1]["name"] + + res = res.select( + "user_idx", + sf.col(f"zip_exp.{item_idx_field_name}").alias("item_idx"), + (sf.lit(-1.0) * sf.col(f"zip_exp.{distance_field_name}")).alias( + "relevance" + ), + ) + return res diff --git a/replay/ann/index_inferers/hnswlib_filter_index_inferer.py b/replay/ann/index_inferers/hnswlib_filter_index_inferer.py new file mode 100644 index 000000000..ab8d3c5f6 --- /dev/null +++ b/replay/ann/index_inferers/hnswlib_filter_index_inferer.py @@ -0,0 +1,77 @@ +import numpy as np +import pandas as pd +from pyspark.sql import DataFrame +from pyspark.sql.pandas.functions import pandas_udf + +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.utils import create_hnswlib_index_instance +from replay.session_handler import State + + +# pylint: disable=too-few-public-methods +class HnswlibFilterIndexInferer(IndexInferer): + """Hnswlib index inferer with filter seen items. Infers hnswlib index.""" + + def infer( + self, vectors: DataFrame, features_col: str, k: int + ) -> DataFrame: + _index_store = self.index_store + index_params = self.index_params + + index_store_broadcast = State().session.sparkContext.broadcast( + _index_store + ) + + @pandas_udf(self.udf_return_type) + def infer_index_udf( + vectors: pd.Series, + num_items: pd.Series, + seen_item_ids: pd.Series, + ) -> pd.DataFrame: + index_store = index_store_broadcast.value + index = index_store.load_index( + init_index=lambda: create_hnswlib_index_instance(index_params), + load_index=lambda index, path: index.load_index(path), + configure_index=lambda index: index.set_ef(index_params.ef_s) + if index_params.ef_s + else None, + ) + + # max number of items to retrieve per batch + max_items_to_retrieve = num_items.max() + + labels, distances = index.knn_query( + np.stack(vectors.values), + k=k + max_items_to_retrieve, + num_threads=1, + ) + + filtered_labels = [] + filtered_distances = [] + for i, item_ids in enumerate(labels): + non_seen_item_indexes = ~np.isin( + item_ids, seen_item_ids[i], assume_unique=True + ) + filtered_labels.append((item_ids[non_seen_item_indexes])[:k]) + filtered_distances.append( + (distances[i][non_seen_item_indexes])[:k] + ) + + pd_res = pd.DataFrame( + { + "item_idx": filtered_labels, + "distance": filtered_distances, + } + ) + + return pd_res + + cols = ["num_items", "seen_item_idxs"] + + res = vectors.select( + "user_idx", + infer_index_udf(features_col, *cols).alias("neighbours"), + ) + res = self._unpack_infer_struct(res) + + return res diff --git a/replay/ann/index_inferers/hnswlib_index_inferer.py b/replay/ann/index_inferers/hnswlib_index_inferer.py new file mode 100644 index 000000000..e7f4e3eed --- /dev/null +++ b/replay/ann/index_inferers/hnswlib_index_inferer.py @@ -0,0 +1,55 @@ +import numpy as np +import pandas as pd +from pyspark.sql import DataFrame +from pyspark.sql.pandas.functions import pandas_udf + +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.utils import create_hnswlib_index_instance +from replay.session_handler import State + + +# pylint: disable=too-few-public-methods +class HnswlibIndexInferer(IndexInferer): + """Hnswlib index inferer without filter seen items. Infers hnswlib index.""" + + def infer( + self, vectors: DataFrame, features_col: str, k: int + ) -> DataFrame: + _index_store = self.index_store + index_params = self.index_params + + index_store_broadcast = State().session.sparkContext.broadcast( + _index_store + ) + + @pandas_udf(self.udf_return_type) + def infer_index_udf(vectors: pd.Series) -> pd.DataFrame: + index_store = index_store_broadcast.value + index = index_store.load_index( + init_index=lambda: create_hnswlib_index_instance(index_params), + load_index=lambda index, path: index.load_index(path), + configure_index=lambda index: index.set_ef(index_params.ef_s) + if index_params.ef_s + else None, + ) + + labels, distances = index.knn_query( + np.stack(vectors.values), + k=k, + num_threads=1, + ) + + pd_res = pd.DataFrame( + {"item_idx": list(labels), "distance": list(distances)} + ) + + return pd_res + + res = vectors.select( + "user_idx", + infer_index_udf(features_col).alias("neighbours"), + ) + + res = self._unpack_infer_struct(res) + + return res diff --git a/replay/ann/index_inferers/nmslib_filter_index_inferer.py b/replay/ann/index_inferers/nmslib_filter_index_inferer.py new file mode 100644 index 000000000..7dd505f86 --- /dev/null +++ b/replay/ann/index_inferers/nmslib_filter_index_inferer.py @@ -0,0 +1,101 @@ +import numpy as np +import pandas as pd +from pyspark.sql import DataFrame +from pyspark.sql.pandas.functions import pandas_udf + +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.utils import ( + get_csr_matrix, +) +from replay.ann.utils import create_nmslib_index_instance +from replay.session_handler import State + + +# pylint: disable=too-few-public-methods +class NmslibFilterIndexInferer(IndexInferer): + """Nmslib index inferer with filter seen items. Infers nmslib hnsw index.""" + + def infer( + self, vectors: DataFrame, features_col: str, k: int + ) -> DataFrame: + _index_store = self.index_store + index_params = self.index_params + + index_store_broadcast = State().session.sparkContext.broadcast( + _index_store + ) + + @pandas_udf(self.udf_return_type) + def infer_index_udf( # pylint: disable=too-many-locals + user_idx: pd.Series, + vector_items: pd.Series, + vector_relevances: pd.Series, + num_items: pd.Series, + seen_item_ids: pd.Series, + ) -> pd.DataFrame: + index_store = index_store_broadcast.value + index = index_store.load_index( + init_index=lambda: create_nmslib_index_instance(index_params), + load_index=lambda index, path: index.loadIndex( + path, load_data=True + ), + configure_index=lambda index: index.setQueryTimeParams( + {"efSearch": index_params.ef_s} + ) + if index_params.ef_s + else None, + ) + + # max number of items to retrieve per batch + max_items_to_retrieve = num_items.max() + + user_vectors = get_csr_matrix( + user_idx, vector_items, vector_relevances + ) + + neighbours = index.knnQueryBatch( + user_vectors[user_idx.values, :], + k=k + max_items_to_retrieve, + num_threads=1, + ) + + neighbours_filtered = [] + for i, (item_idxs, distances) in enumerate(neighbours): + non_seen_item_indexes = ~np.isin( + item_idxs, seen_item_ids[i], assume_unique=True + ) + neighbours_filtered.append( + ( + (item_idxs[non_seen_item_indexes])[:k], + (distances[non_seen_item_indexes])[:k], + ) + ) + + pd_res = pd.DataFrame( + neighbours_filtered, columns=["item_idx", "distance"] + ) + + # pd_res looks like + # item_idx distances + # [1, 2, 3, ...] [-0.5, -0.3, -0.1, ...] + # [1, 3, 4, ...] [-0.1, -0.8, -0.2, ...] + + return pd_res + + cols = [ + "user_idx", + "vector_items", + "vector_relevances", + "num_items", + "seen_item_idxs", + ] + # cols = cols + ["num_items", "seen_item_idxs"] + + res = vectors.select( + "user_idx", + infer_index_udf(*cols).alias("neighbours"), + ) + + res = self._unpack_infer_struct(res) + + return res diff --git a/replay/ann/index_inferers/nmslib_index_inferer.py b/replay/ann/index_inferers/nmslib_index_inferer.py new file mode 100644 index 000000000..cd1923b54 --- /dev/null +++ b/replay/ann/index_inferers/nmslib_index_inferer.py @@ -0,0 +1,71 @@ +import pandas as pd +from pyspark.sql import DataFrame +from pyspark.sql.pandas.functions import pandas_udf + +from replay.ann.index_inferers.base_inferer import IndexInferer +from replay.ann.index_inferers.utils import ( + get_csr_matrix, +) +from replay.ann.utils import create_nmslib_index_instance +from replay.session_handler import State + + +# pylint: disable=too-few-public-methods +class NmslibIndexInferer(IndexInferer): + """Nmslib index inferer without filter seen items. Infers nmslib hnsw index.""" + + def infer( + self, vectors: DataFrame, features_col: str, k: int + ) -> DataFrame: + _index_store = self.index_store + index_params = self.index_params + + index_store_broadcast = State().session.sparkContext.broadcast( + _index_store + ) + + @pandas_udf(self.udf_return_type) + def infer_index_udf( + user_idx: pd.Series, + vector_items: pd.Series, + vector_relevances: pd.Series, + ) -> pd.DataFrame: + index_store = index_store_broadcast.value + index = index_store.load_index( + init_index=lambda: create_nmslib_index_instance(index_params), + load_index=lambda index, path: index.loadIndex( + path, load_data=True + ), + configure_index=lambda index: index.setQueryTimeParams( + {"efSearch": index_params.ef_s} + ) + if index_params.ef_s + else None, + ) + + user_vectors = get_csr_matrix( + user_idx, vector_items, vector_relevances + ) + neighbours = index.knnQueryBatch( + user_vectors[user_idx.values, :], k=k, num_threads=1 + ) + + pd_res = pd.DataFrame(neighbours, columns=["item_idx", "distance"]) + + # pd_res looks like + # item_idx distances + # [1, 2, 3, ...] [-0.5, -0.3, -0.1, ...] + # [1, 3, 4, ...] [-0.1, -0.8, -0.2, ...] + + return pd_res + + cols = ["user_idx", "vector_items", "vector_relevances"] + + res = vectors.select( + "user_idx", + infer_index_udf(*cols).alias("neighbours"), + ) + + res = self._unpack_infer_struct(res) + + return res diff --git a/replay/ann/index_inferers/utils.py b/replay/ann/index_inferers/utils.py new file mode 100644 index 000000000..c74592598 --- /dev/null +++ b/replay/ann/index_inferers/utils.py @@ -0,0 +1,30 @@ +import pandas as pd +from scipy.sparse import csr_matrix + + +def get_csr_matrix( + user_idx: pd.Series, + vector_items: pd.Series, + vector_relevances: pd.Series, +) -> csr_matrix: + """Creates and returns csr matrix of user-item interactions""" + return csr_matrix( + ( + vector_relevances.explode().values.astype(float), + ( + user_idx.repeat( + vector_items.apply( + lambda x: len(x) # pylint: disable=unnecessary-lambda + ) + ).values, + vector_items.explode().values.astype(int), + ), + ), + shape=( + user_idx.max() + 1, + vector_items.apply( + lambda x: max(x) # pylint: disable=unnecessary-lambda + ).max() + + 1, + ), + ) diff --git a/replay/ann/index_stores/__init__.py b/replay/ann/index_stores/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/replay/ann/index_stores/base_index_store.py b/replay/ann/index_stores/base_index_store.py new file mode 100644 index 000000000..35ca24941 --- /dev/null +++ b/replay/ann/index_stores/base_index_store.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from typing import Callable, Any + + +class IndexStore(ABC): + """Abstract base class for index stores. Describes a common interface for index stores.""" + + def __init__(self, cleanup: bool = True): + self._index = None + self.cleanup = cleanup + + @abstractmethod + def save_to_store(self, save_index: Callable[[str], None]): + """Dumps index file to store""" + + @abstractmethod + def load_index( + self, + init_index: Callable[[], None], + load_index: Callable[[Any, str], None], + configure_index: Callable[[Any], None], + ) -> Any: + """Loads index from IndexStore to index instance. + If the index has already been loaded before, + the previously loaded index instance is returned.""" + + @abstractmethod + def dump_index(self, target_path: str): + """Dumps index files to `target_path`""" diff --git a/replay/ann/index_stores/hdfs_index_store.py b/replay/ann/index_stores/hdfs_index_store.py new file mode 100644 index 000000000..d53ecf7ac --- /dev/null +++ b/replay/ann/index_stores/hdfs_index_store.py @@ -0,0 +1,93 @@ +import logging +import os +import tempfile +import weakref +from typing import Callable, Any + +from pyarrow import fs + +from replay.ann.index_stores.base_index_store import IndexStore +from replay.utils import get_filesystem, FileSystem + +logger = logging.getLogger("replay") + + +class HdfsIndexStore(IndexStore): + """Class that responsible for index store in HDFS.""" + + def __init__(self, warehouse_dir: str, index_dir: str): + index_dir_path = os.path.join(warehouse_dir, index_dir) + self._index_dir_info = get_filesystem(index_dir_path) + if self._index_dir_info.filesystem != FileSystem.HDFS: + raise ValueError( + f"Can't recognize path {index_dir_path} as HDFS path!" + ) + self._hadoop_fs = fs.HadoopFileSystem.from_uri( + self._index_dir_info.hdfs_uri + ) + super().__init__() + + if self.cleanup: + logger.debug( + "Index directory %s is marked for deletion via weakref.finalize()", + self._index_dir_info.path, + ) + weakref.finalize( + self, self._hadoop_fs.delete_dir, self._index_dir_info.path + ) + + def load_index( + self, + init_index: Callable[[], None], + load_index: Callable[[Any, str], None], + configure_index: Callable[[Any], None], + ): + if self._index: + return self._index + + self._index = init_index() + + with tempfile.TemporaryDirectory() as temp_dir: + temp_file_path = os.path.join(temp_dir, "index") + + # here we copy index files from hdfs directory + # to local disk directory + fs.copy_files( + self._index_dir_info.path, + "file://" + temp_dir, + source_filesystem=self._hadoop_fs, + ) + load_index(self._index, temp_file_path) + configure_index(self._index) + + return self._index + + def save_to_store(self, save_index: Callable[[str], None]): + with tempfile.TemporaryDirectory() as temp_dir: + temp_file_path = os.path.join(temp_dir, "index") + save_index(temp_file_path) + + # here we copy index files from local disk directory + # to hdfs directory + fs.copy_files( + "file://" + temp_dir, + self._index_dir_info.path, + destination_filesystem=self._hadoop_fs, + ) + # param use_threads=True (?) + logger.info("Index files saved to %s", self._index_dir_info.path) + + def dump_index(self, target_path: str): + target_path_info = get_filesystem(target_path) + destination_filesystem, target_path = fs.FileSystem.from_uri( + target_path_info.hdfs_uri + target_path_info.path + if target_path_info.filesystem == FileSystem.HDFS + else target_path_info.path + ) + target_path = os.path.join(target_path, "index_files") + fs.copy_files( + self._index_dir_info.path, + target_path, + source_filesystem=self._hadoop_fs, + destination_filesystem=destination_filesystem, + ) diff --git a/replay/ann/index_stores/shared_disk_index_store.py b/replay/ann/index_stores/shared_disk_index_store.py new file mode 100644 index 000000000..2cab6750e --- /dev/null +++ b/replay/ann/index_stores/shared_disk_index_store.py @@ -0,0 +1,66 @@ +import logging +import os +import shutil +import weakref +from pathlib import Path +from typing import Callable, Any + +from pyarrow import fs + +from replay.ann.index_stores.base_index_store import IndexStore + + +logger = logging.getLogger("replay") + + +class SharedDiskIndexStore(IndexStore): + """Class that responsible for index store in shared disk. + It can also be used with a local disk when the driver and executors + are running on the same machine.""" + + def __init__( + self, warehouse_dir: str, index_dir: str, cleanup: bool = True + ): + self.index_dir_path = os.path.join(warehouse_dir, index_dir) + super().__init__(cleanup) + if self.cleanup: + logger.debug( + "Index directory %s is marked for deletion via weakref.finalize()", + self.index_dir_path, + ) + weakref.finalize(self, shutil.rmtree, self.index_dir_path) + + def load_index( + self, + init_index: Callable[[], None], + load_index: Callable[[Any, str], None], + configure_index: Callable[[Any], None], + ): + if self._index: + return self._index + + self._index = init_index() + + temp_file_path = os.path.join(self.index_dir_path, "index") + load_index(self._index, temp_file_path) + configure_index(self._index) + + return self._index + + def save_to_store(self, save_index: Callable[[str], None]): + Path(self.index_dir_path).mkdir(parents=True, exist_ok=True) + temp_file_path = os.path.join(self.index_dir_path, "index") + save_index(temp_file_path) + + def dump_index(self, target_path: str): + destination_filesystem, target_path = fs.FileSystem.from_uri( + target_path + ) + target_path = os.path.join(target_path, "index_files") + destination_filesystem.create_dir(target_path) + fs.copy_files( + self.index_dir_path, + target_path, + source_filesystem=fs.LocalFileSystem(), + destination_filesystem=destination_filesystem, + ) diff --git a/replay/ann/index_stores/spark_files_index_store.py b/replay/ann/index_stores/spark_files_index_store.py new file mode 100644 index 000000000..7db4f032c --- /dev/null +++ b/replay/ann/index_stores/spark_files_index_store.py @@ -0,0 +1,101 @@ +import logging +import os +import shutil +import tempfile +import weakref +from typing import Callable, Any + +from pyarrow import fs +from pyspark import SparkFiles + +from replay.ann.index_stores.base_index_store import IndexStore +from replay.session_handler import State +from replay.utils import get_filesystem, FileSystem + + +logger = logging.getLogger("replay") + + +class SparkFilesIndexStore(IndexStore): + """Class that responsible for index store in spark files. + Works though SparkContext.addFile().""" + + def _clean_up(self): + """Removes directory with index files + before the instance is garbage collected.""" + if self.index_dir_path: + shutil.rmtree(self.index_dir_path) + + def __init__(self, cleanup: bool = True): + self.index_dir_path = None + super().__init__(cleanup) + if self.cleanup: + weakref.finalize(self, self._clean_up) + + def load_index( + self, + init_index: Callable[[], None], + load_index: Callable[[Any, str], None], + configure_index: Callable[[Any], None], + ): + if self._index: + return self._index + + self._index = init_index() + + temp_file_path = SparkFiles.get("index") + load_index(self._index, temp_file_path) + configure_index(self._index) + + return self._index + + def save_to_store(self, save_index: Callable[[str], None]): + self.index_dir_path: str = tempfile.mkdtemp() + temp_file_path = os.path.join(self.index_dir_path, "index") + save_index(temp_file_path) + + spark = State().session + for filename in os.listdir(self.index_dir_path): + index_file_path = os.path.join(self.index_dir_path, filename) + spark.sparkContext.addFile("file://" + index_file_path) + logger.info( + "Index file %s transferred to executors", index_file_path + ) + + def dump_index(self, target_path: str): + destination_filesystem, target_path = fs.FileSystem.from_uri( + target_path + ) + target_path = os.path.join(target_path, "index_files") + destination_filesystem.create_dir(target_path) + fs.copy_files( + self.index_dir_path, + target_path, + source_filesystem=fs.LocalFileSystem(), + destination_filesystem=destination_filesystem, + ) + + def load_from_path(self, path: str): + """Loads index from `path` directory to spark files.""" + path_info = get_filesystem(path) + source_filesystem, path = fs.FileSystem.from_uri( + path_info.hdfs_uri + path_info.path + if path_info.filesystem == FileSystem.HDFS + else path_info.path + ) + path = os.path.join(path, "index_files") + self.index_dir_path: str = tempfile.mkdtemp() + fs.copy_files( + path, + self.index_dir_path, + source_filesystem=source_filesystem, + destination_filesystem=fs.LocalFileSystem(), + ) + + spark = State().session + for filename in os.listdir(self.index_dir_path): + index_file_path = os.path.join(self.index_dir_path, filename) + spark.sparkContext.addFile("file://" + index_file_path) + logger.info( + "Index file %s transferred to executors", index_file_path + ) diff --git a/replay/ann/utils.py b/replay/ann/utils.py new file mode 100644 index 000000000..37e57923d --- /dev/null +++ b/replay/ann/utils.py @@ -0,0 +1,46 @@ +import hnswlib +import nmslib + +from replay.ann.entities.hnswlib_param import HnswlibParam +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam + + +def create_hnswlib_index_instance(params: HnswlibParam, init: bool = False): + """ + Creates and returns hnswlib index + + :param params: `HnswlibParam` + :param init: If `True` it will call the `init_index` method on the index. + Used when we want to create a new index. + If `False` then the index will be used to load index data from a file. + :return: `hnswlib` index instance + """ + index = hnswlib.Index( # pylint: disable=c-extension-no-member + space=params.space, dim=params.dim + ) + + if init: + # Initializing index - the maximum number of elements should be known beforehand + index.init_index( + max_elements=params.max_elements, + ef_construction=params.ef_c, + M=params.m, + ) + + return index + + +def create_nmslib_index_instance(params: NmslibHnswParam): + """ + Creates and returns nmslib index + + :param params: `NmslibHnswParam` + :return: `nmslib` index + """ + index = nmslib.init( # pylint: disable=c-extension-no-member + method=params.method, + space=params.space, + data_type=nmslib.DataType.SPARSE_VECTOR, # pylint: disable=c-extension-no-member + ) + + return index diff --git a/replay/models/__init__.py b/replay/models/__init__.py index 2d25953e4..31c238848 100644 --- a/replay/models/__init__.py +++ b/replay/models/__init__.py @@ -28,3 +28,4 @@ from replay.models.cat_pop_rec import CatPopRec from replay.models.ucb import UCB from replay.models.thompson_sampling import ThompsonSampling +from replay.models.distributed_lightfm_wrap.lightfm_wrap import DistributedLightFMWrap diff --git a/replay/models/admm_slim.py b/replay/models/admm_slim.py index 108566161..b7ba60cf3 100644 --- a/replay/models/admm_slim.py +++ b/replay/models/admm_slim.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict, Any import numba as nb import numpy as np @@ -6,7 +6,8 @@ from pyspark.sql import DataFrame from scipy.sparse import coo_matrix, csr_matrix -from replay.models.base_rec import NeighbourRec +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam +from replay.models.base_neighbour_rec import NeighbourRec from replay.session_handler import State @@ -65,7 +66,7 @@ def _main_iteration( ) -# pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-instance-attributes, too-many-ancestors class ADMMSLIM(NeighbourRec): """`ADMM SLIM: Sparse Recommendations for Many Users `_ @@ -74,6 +75,12 @@ class ADMMSLIM(NeighbourRec): Recommendations are improved with Alternating Direction Method of Multipliers. """ + def _get_ann_infer_params(self) -> Dict[str, Any]: + return { + "features_col": None, + "params": self._nmslib_hnsw_params, + } + rho: float threshold: float = 5 multiplicator: float = 2 @@ -93,11 +100,14 @@ def __init__( lambda_1: float = 5, lambda_2: float = 5000, seed: Optional[int] = None, + nmslib_hnsw_params: Optional[NmslibHnswParam] = None, ): """ :param lambda_1: l1 regularization term :param lambda_2: l2 regularization term :param seed: random seed + :param nmslib_hnsw_params: `NmslibHnswParam` instance, parameters for nmslib-hnsw methods. + If not set, then ann will not be used. """ if lambda_1 < 0 or lambda_2 <= 0: raise ValueError("Invalid regularization parameters") @@ -105,6 +115,7 @@ def __init__( self.lambda_2 = lambda_2 self.rho = lambda_2 self.seed = seed + self._nmslib_hnsw_params = nmslib_hnsw_params @property def _init_args(self): diff --git a/replay/models/als.py b/replay/models/als.py index 19a6c97cd..804ed8dd4 100644 --- a/replay/models/als.py +++ b/replay/models/als.py @@ -1,20 +1,47 @@ -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict, Any import pyspark.sql.functions as sf - from pyspark.ml.recommendation import ALS, ALSModel from pyspark.sql import DataFrame from pyspark.sql.types import DoubleType +from replay.ann.ann_mixin import ANNMixin +from replay.ann.index_builders.base_index_builder import IndexBuilder from replay.models.base_rec import Recommender, ItemVectorModel from replay.utils import list_to_vector_udf -class ALSWrap(Recommender, ItemVectorModel): +# pylint: disable=too-many-instance-attributes, too-many-ancestors +class ALSWrap(Recommender, ItemVectorModel, ANNMixin): """Wrapper for `Spark ALS `_. """ + def _get_ann_infer_params(self) -> Dict[str, Any]: + self.index_builder.index_params.dim = self.rank + return { + "features_col": "user_factors", + # "params": self._hnswlib_params, + } + + def _get_vectors_to_infer_ann_inner(self, log: DataFrame, users: DataFrame) -> DataFrame: + user_vectors, _ = self.get_features(users) + return user_vectors + + def _get_ann_build_params(self, log: DataFrame): + self.index_builder.index_params.dim = self.rank + self.index_builder.index_params.max_elements = log.select("item_idx").distinct().count() + return { + "features_col": "item_factors", + "ids_col": "item_idx", + } + + def _get_vectors_to_build_ann(self, log: DataFrame) -> DataFrame: + item_vectors, _ = self.get_features( + log.select("item_idx").distinct() + ) + return item_vectors + _seed: Optional[int] = None _search_space = { "rank": {"type": "loguniform_int", "args": [8, 256]}, @@ -28,6 +55,7 @@ def __init__( seed: Optional[int] = None, num_item_blocks: Optional[int] = None, num_user_blocks: Optional[int] = None, + index_builder: Optional[IndexBuilder] = None, ): """ :param rank: hidden dimension for the approximate matrix @@ -45,6 +73,11 @@ def __init__( self._seed = seed self._num_item_blocks = num_item_blocks self._num_user_blocks = num_user_blocks + if isinstance(index_builder, (IndexBuilder, type(None))): + self.index_builder = index_builder + elif isinstance(index_builder, dict): + self.init_builder_from_dict(index_builder) + self.num_elements = None @property def _init_args(self): @@ -52,16 +85,27 @@ def _init_args(self): "rank": self.rank, "implicit_prefs": self.implicit_prefs, "seed": self._seed, + "index_builder": self.index_builder.init_meta_as_dict() if self.index_builder else None, } def _save_model(self, path: str): self.model.write().overwrite().save(path) + # if self._hnswlib_params: + # self._save_hnswlib_index(path) + if self._use_ann: + self._save_index(path) + def _load_model(self, path: str): self.model = ALSModel.load(path) self.model.itemFactors.cache() self.model.userFactors.cache() + # if self._hnswlib_params: + # self._load_hnswlib_index(path) + if self._use_ann: + self._load_index(path) + def _fit( self, log: DataFrame, diff --git a/replay/models/association_rules.py b/replay/models/association_rules.py index ef328476f..9cb99d80d 100644 --- a/replay/models/association_rules.py +++ b/replay/models/association_rules.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional, Union +from typing import Iterable, List, Optional, Union, Dict, Any import numpy as np @@ -7,9 +7,11 @@ from pyspark.sql import DataFrame from pyspark.sql.window import Window -from replay.models.base_rec import NeighbourRec +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam +from replay.models.base_neighbour_rec import NeighbourRec +# pylint: disable=too-many-ancestors, too-many-instance-attributes class AssociationRulesItemRec(NeighbourRec): """ Item-to-item recommender based on association rules. @@ -70,6 +72,12 @@ class AssociationRulesItemRec(NeighbourRec): In this case all items in sessions should have the same relevance. """ + def _get_ann_infer_params(self) -> Dict[str, Any]: + return { + "features_col": None, + "params": self._nmslib_hnsw_params, + } + can_predict_item_to_item = True item_to_item_metrics: List[str] = ["lift", "confidence", "confidence_gain"] similarity: DataFrame @@ -94,6 +102,7 @@ def __init__( num_neighbours: Optional[int] = 1000, use_relevance: bool = False, similarity_metric: str = "confidence", + nmslib_hnsw_params: Optional[NmslibHnswParam] = None, ) -> None: """ :param session_col: name of column to group sessions. @@ -107,6 +116,8 @@ def __init__( :param similarity_metric: `lift` of 'confidence' The metric used as a similarity to calculate the prediction, one of [``lift``, ``confidence``, ``confidence_gain``] + :param nmslib_hnsw_params: `NmslibHnswParam` instance, parameters for nmslib-hnsw methods. + If not set, then ann will not be used. """ self.session_col = ( @@ -117,6 +128,7 @@ def __init__( self.num_neighbours = num_neighbours self.use_relevance = use_relevance self.similarity_metric = similarity_metric + self._nmslib_hnsw_params = nmslib_hnsw_params @property def _init_args(self): diff --git a/replay/models/base_neighbour_rec.py b/replay/models/base_neighbour_rec.py new file mode 100644 index 000000000..8c85ee555 --- /dev/null +++ b/replay/models/base_neighbour_rec.py @@ -0,0 +1,227 @@ +# pylint: disable=too-many-lines +""" +NeighbourRec - base class that requires log at prediction time. +Part of set of abstract classes (from base_rec.py) +""" + +from abc import ABC +from typing import ( + Any, + Dict, + Iterable, + Optional, + Union, +) + +from pyspark.sql import DataFrame +from pyspark.sql import functions as sf +from pyspark.sql.column import Column + +from replay.ann.ann_mixin import ANNMixin +from replay.models.base_rec import Recommender + + +class NeighbourRec(Recommender, ANNMixin, ABC): + """Base class that requires log at prediction time""" + + similarity: Optional[DataFrame] + can_predict_item_to_item: bool = True + can_predict_cold_users: bool = True + can_change_metric: bool = False + item_to_item_metrics = ["similarity"] + _similarity_metric = "similarity" + + @property + def _dataframes(self): + return {"similarity": self.similarity} + + def _clear_cache(self): + if hasattr(self, "similarity"): + self.similarity.unpersist() + + # pylint: disable=missing-function-docstring + @property + def similarity_metric(self): + return self._similarity_metric + + @similarity_metric.setter + def similarity_metric(self, value): + if not self.can_change_metric: + raise ValueError( + "This class does not support changing similarity metrics" + ) + if value not in self.item_to_item_metrics: + raise ValueError( + f"Select one of the valid metrics for predict: " + f"{self.item_to_item_metrics}" + ) + self._similarity_metric = value + + def _predict_pairs_inner( + self, + log: DataFrame, + filter_df: DataFrame, + condition: Column, + users: DataFrame, + ) -> DataFrame: + """ + Get recommendations for all provided users + and filter results with ``filter_df`` by ``condition``. + It allows to implement both ``predict_pairs`` and usual ``predict``@k. + + :param log: historical interactions, DataFrame + ``[user_idx, item_idx, timestamp, relevance]``. + :param filter_df: DataFrame use to filter items: + ``[item_idx_filter]`` or ``[user_idx_filter, item_idx_filter]``. + :param condition: condition used for inner join with ``filter_df`` + :param users: users to calculate recommendations for + :return: DataFrame ``[user_idx, item_idx, relevance]`` + """ + if log is None: + raise ValueError( + "log is not provided, but it is required for prediction" + ) + + recs = ( + log.join(users, how="inner", on="user_idx") + .join( + self.similarity, + how="inner", + on=sf.col("item_idx") == sf.col("item_idx_one"), + ) + .join( + filter_df, + how="inner", + on=condition, + ) + .groupby("user_idx", "item_idx_two") + .agg(sf.sum(self.similarity_metric).alias("relevance")) + .withColumnRenamed("item_idx_two", "item_idx") + ) + return recs + + # pylint: disable=too-many-arguments + def _predict( + self, + log: DataFrame, + k: int, + users: DataFrame, + items: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + filter_seen_items: bool = True, + ) -> DataFrame: + + return self._predict_pairs_inner( + log=log, + filter_df=items.withColumnRenamed("item_idx", "item_idx_filter"), + condition=sf.col("item_idx_two") == sf.col("item_idx_filter"), + users=users, + ) + + def _predict_pairs( + self, + pairs: DataFrame, + log: Optional[DataFrame] = None, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + ) -> DataFrame: + + if log is None: + raise ValueError( + "log is not provided, but it is required for prediction" + ) + + return self._predict_pairs_inner( + log=log, + filter_df=( + pairs.withColumnRenamed( + "user_idx", "user_idx_filter" + ).withColumnRenamed("item_idx", "item_idx_filter") + ), + condition=(sf.col("user_idx") == sf.col("user_idx_filter")) + & (sf.col("item_idx_two") == sf.col("item_idx_filter")), + users=pairs.select("user_idx").distinct(), + ) + + def get_nearest_items( + self, + items: Union[DataFrame, Iterable], + k: int, + metric: Optional[str] = None, + candidates: Optional[Union[DataFrame, Iterable]] = None, + ) -> DataFrame: + """ + Get k most similar items be the `metric` for each of the `items`. + + :param items: spark dataframe or list of item ids to find neighbors + :param k: number of neighbors + :param metric: metric is not used to find neighbours in NeighbourRec, + the parameter is ignored + :param candidates: spark dataframe or list of items + to consider as similar, e.g. popular/new items. If None, + all items presented during model training are used. + :return: dataframe with the most similar items an distance, + where bigger value means greater similarity. + spark-dataframe with columns ``[item_idx, neighbour_item_idx, similarity]`` + """ + + if metric is not None: + self.logger.debug( + "Metric is not used to determine nearest items in %s model", + str(self), + ) + + return self._get_nearest_items_wrap( + items=items, + k=k, + metric=metric, + candidates=candidates, + ) + + def _get_nearest_items( + self, + items: DataFrame, + metric: Optional[str] = None, + candidates: Optional[DataFrame] = None, + ) -> DataFrame: + + similarity_filtered = self.similarity.join( + items.withColumnRenamed("item_idx", "item_idx_one"), + on="item_idx_one", + ) + + if candidates is not None: + similarity_filtered = similarity_filtered.join( + candidates.withColumnRenamed("item_idx", "item_idx_two"), + on="item_idx_two", + ) + + return similarity_filtered.select( + "item_idx_one", + "item_idx_two", + "similarity" if metric is None else metric, + ) + + def _get_ann_build_params(self, log: DataFrame) -> Dict[str, Any]: + self.index_builder.index_params.items_count = log.select(sf.max("item_idx")).first()[0] + 1 + return { + "features_col": None, + } + + def _get_vectors_to_build_ann(self, log: DataFrame) -> DataFrame: + similarity_df = self.similarity.select( + "similarity", "item_idx_one", "item_idx_two" + ) + return similarity_df + + def _get_vectors_to_infer_ann_inner( + self, log: DataFrame, users: DataFrame + ) -> DataFrame: + + user_vectors = ( + log.groupBy("user_idx").agg( + sf.collect_list("item_idx").alias("vector_items"), + sf.collect_list("relevance").alias("vector_relevances")) + ) + return user_vectors diff --git a/replay/models/base_rec.py b/replay/models/base_rec.py index 8be7fa625..c0e8f4b8b 100644 --- a/replay/models/base_rec.py +++ b/replay/models/base_rec.py @@ -544,7 +544,7 @@ def _predict_wrap( message = f"k = {k} > number of items = {num_items}" self.logger.debug(message) - recs = self._predict( + recs = self._inner_predict_wrap( log, k, users, @@ -630,6 +630,48 @@ def _predict( ``[user_idx, item_idx, relevance]`` """ + def _inner_predict_wrap( + self, + log: DataFrame, + k: int, + users: DataFrame, + items: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + filter_seen_items: bool = True, + ) -> DataFrame: + """ + Inner method that wrap _predict method. Can be overwritten. + + :param log: historical log of interactions + ``[user_idx, item_idx, timestamp, relevance]`` + :param k: number of recommendations for each user + :param users: users to create recommendations for + dataframe containing ``[user_idx]`` or ``array-like``; + if ``None``, recommend to all users from ``log`` + :param items: candidate items for recommendations + dataframe containing ``[item_idx]`` or ``array-like``; + if ``None``, take all items from ``log``. + If it contains new items, ``relevance`` for them will be ``0``. + :param user_features: user features + ``[user_idx , timestamp]`` + feature columns + :param item_features: item features + ``[item_idx , timestamp]`` + feature columns + :param filter_seen_items: flag to remove seen items from recommendations based on ``log``. + :return: recommendation dataframe + ``[user_idx, item_idx, relevance]`` + """ + + return self._predict( + log, + k, + users, + items, + user_features, + item_features, + filter_seen_items, + ) + def _get_fit_counts(self, entity: str) -> int: if not hasattr(self, f"_num_{entity}s"): setattr( @@ -1405,189 +1447,6 @@ def predict_pairs( ) -class NeighbourRec(Recommender, ABC): - """Base class that requires log at prediction time""" - - similarity: Optional[DataFrame] - can_predict_item_to_item: bool = True - can_predict_cold_users: bool = True - can_change_metric: bool = False - item_to_item_metrics = ["similarity"] - _similarity_metric = "similarity" - - @property - def _dataframes(self): - return {"similarity": self.similarity} - - def _clear_cache(self): - if hasattr(self, "similarity"): - self.similarity.unpersist() - - # pylint: disable=missing-function-docstring - @property - def similarity_metric(self): - return self._similarity_metric - - @similarity_metric.setter - def similarity_metric(self, value): - if not self.can_change_metric: - raise ValueError( - "This class does not support changing similarity metrics" - ) - if value not in self.item_to_item_metrics: - raise ValueError( - f"Select one of the valid metrics for predict: " - f"{self.item_to_item_metrics}" - ) - self._similarity_metric = value - - def _predict_pairs_inner( - self, - log: DataFrame, - filter_df: DataFrame, - condition: Column, - users: DataFrame, - ) -> DataFrame: - """ - Get recommendations for all provided users - and filter results with ``filter_df`` by ``condition``. - It allows to implement both ``predict_pairs`` and usual ``predict``@k. - - :param log: historical interactions, DataFrame - ``[user_idx, item_idx, timestamp, relevance]``. - :param filter_df: DataFrame use to filter items: - ``[item_idx_filter]`` or ``[user_idx_filter, item_idx_filter]``. - :param condition: condition used for inner join with ``filter_df`` - :param users: users to calculate recommendations for - :return: DataFrame ``[user_idx, item_idx, relevance]`` - """ - if log is None: - raise ValueError( - "log is not provided, but it is required for prediction" - ) - - recs = ( - log.join(users, how="inner", on="user_idx") - .join( - self.similarity, - how="inner", - on=sf.col("item_idx") == sf.col("item_idx_one"), - ) - .join( - filter_df, - how="inner", - on=condition, - ) - .groupby("user_idx", "item_idx_two") - .agg(sf.sum(self.similarity_metric).alias("relevance")) - .withColumnRenamed("item_idx_two", "item_idx") - ) - return recs - - # pylint: disable=too-many-arguments - def _predict( - self, - log: DataFrame, - k: int, - users: DataFrame, - items: DataFrame, - user_features: Optional[DataFrame] = None, - item_features: Optional[DataFrame] = None, - filter_seen_items: bool = True, - ) -> DataFrame: - - return self._predict_pairs_inner( - log=log, - filter_df=items.withColumnRenamed("item_idx", "item_idx_filter"), - condition=sf.col("item_idx_two") == sf.col("item_idx_filter"), - users=users, - ) - - def _predict_pairs( - self, - pairs: DataFrame, - log: Optional[DataFrame] = None, - user_features: Optional[DataFrame] = None, - item_features: Optional[DataFrame] = None, - ) -> DataFrame: - - if log is None: - raise ValueError( - "log is not provided, but it is required for prediction" - ) - - return self._predict_pairs_inner( - log=log, - filter_df=( - pairs.withColumnRenamed( - "user_idx", "user_idx_filter" - ).withColumnRenamed("item_idx", "item_idx_filter") - ), - condition=(sf.col("user_idx") == sf.col("user_idx_filter")) - & (sf.col("item_idx_two") == sf.col("item_idx_filter")), - users=pairs.select("user_idx").distinct(), - ) - - def get_nearest_items( - self, - items: Union[DataFrame, Iterable], - k: int, - metric: Optional[str] = None, - candidates: Optional[Union[DataFrame, Iterable]] = None, - ) -> DataFrame: - """ - Get k most similar items be the `metric` for each of the `items`. - - :param items: spark dataframe or list of item ids to find neighbors - :param k: number of neighbors - :param metric: metric is not used to find neighbours in NeighbourRec, - the parameter is ignored - :param candidates: spark dataframe or list of items - to consider as similar, e.g. popular/new items. If None, - all items presented during model training are used. - :return: dataframe with the most similar items an distance, - where bigger value means greater similarity. - spark-dataframe with columns ``[item_idx, neighbour_item_idx, similarity]`` - """ - - if metric is not None: - self.logger.debug( - "Metric is not used to determine nearest items in %s model", - str(self), - ) - - return self._get_nearest_items_wrap( - items=items, - k=k, - metric=metric, - candidates=candidates, - ) - - def _get_nearest_items( - self, - items: DataFrame, - metric: Optional[str] = None, - candidates: Optional[DataFrame] = None, - ) -> DataFrame: - - similarity_filtered = self.similarity.join( - items.withColumnRenamed("item_idx", "item_idx_one"), - on="item_idx_one", - ) - - if candidates is not None: - similarity_filtered = similarity_filtered.join( - candidates.withColumnRenamed("item_idx", "item_idx_two"), - on="item_idx_two", - ) - - return similarity_filtered.select( - "item_idx_one", - "item_idx_two", - "similarity" if metric is None else metric, - ) - - class NonPersonalizedRecommender(Recommender, ABC): """Base class for non-personalized recommenders with popularity statistics.""" diff --git a/replay/models/distributed_lightfm_wrap/lightfm_wrap.py b/replay/models/distributed_lightfm_wrap/lightfm_wrap.py new file mode 100644 index 000000000..3851cadc9 --- /dev/null +++ b/replay/models/distributed_lightfm_wrap/lightfm_wrap.py @@ -0,0 +1,757 @@ +import logging +import multiprocessing as mp +from datetime import timedelta +from os.path import join +from typing import Any, Dict, Optional, Tuple, List + +import numpy as np +import pandas as pd +from lightfm import LightFM +from pyspark.sql import DataFrame, SparkSession +from pyspark.sql import functions as sf +from pyspark.sql.types import ( + ArrayType, + FloatType, + DoubleType, + IntegerType, + StructField, + StructType, +) +import pygloo as pgl +import scipy.sparse as sp +from sklearn.preprocessing import MinMaxScaler +import torch.distributed as dist + +from replay.models.base_rec import HybridRecommender +from replay.models.distributed_lightfm_wrap.utils import LightFMTraining +from replay.ann.ann_mixin import ANNMixin +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.session_handler import State +from replay.utils import ( + check_numeric, + load_pickled_from_parquet, + save_picklable_to_parquet, +) + +logger = logging.getLogger(__name__) + + +# pylint: disable=too-many-locals, too-many-instance-attributes, too-many-arguments, unnecessary-dunder-call +class DistributedLightFMWrap(HybridRecommender, ANNMixin): + """Wrapper for distributed version of the LightFM.""" + + model_weights = ( + "item_embeddings", + "item_embedding_gradients", + "item_embedding_momentum", + "item_biases", + "item_bias_gradients", + "item_bias_momentum", + "user_embeddings", + "user_embedding_gradients", + "user_embedding_momentum", + "user_biases", + "user_bias_gradients", + "user_bias_momentum", + ) + losses = ["warp", "bpr", "warp-kos", "logistic"] + + @staticmethod + def _get_entity_name(df: DataFrame): + return "item" if "item_idx" in df.columns else "user" + + def _get_features( + self, ids: DataFrame, features: Optional[DataFrame], + ) -> Tuple[Optional[DataFrame], Optional[int]]: + if self.model is None: + raise AttributeError("Model has not been fitted yet.") + entity = self._get_entity_name(ids) + if features is not None: + features = self._convert_features_to_csr( + ids.select(f"{entity}_idx").distinct(), features + ) + _biases, representations = self.model.__getattribute__( + f"get_{entity}_representations" + )(features=features) + + def _representations( + representations_arr: np.ndarray, biases_arr: np.ndarray + ): + biases_arr = biases_arr.tolist() + for entity_idx in range(representations_arr.shape[0]): + yield entity_idx, representations_arr[ + entity_idx + ].tolist(), biases_arr[entity_idx] + + lightfm_factors = State().session.createDataFrame( + _representations(representations, _biases), + schema=StructType( + [ + StructField(f"{entity}_idx", IntegerType()), + StructField(f"{entity}_factors", ArrayType(DoubleType())), + StructField(f"{entity}_bias", DoubleType()), + ] + ), + ) + return ( + lightfm_factors.join(ids, how="right", on=f"{entity}_idx"), + self.model.no_components, + ) + + def _get_vectors_to_build_ann(self, log: DataFrame) -> DataFrame: + item_vectors, _ = self._get_features( + log.select("item_idx").distinct(), None + ) + return item_vectors + + def _get_ann_build_params(self, log: DataFrame) -> Dict[str, Any]: + self.index_builder.index_params.dim = self.model.no_components + self.index_builder.index_params.max_elements = log.select("item_idx").distinct().count() + return { + "features_col": "item_factors", + "ids_col": "item_idx", + } + + def _get_vectors_to_infer_ann_inner( + self, log: DataFrame, users: DataFrame + ) -> DataFrame: + user_vectors, _ = self._get_features(users, None) + return user_vectors + + def _get_ann_infer_params(self) -> Dict[str, Any]: + self.index_builder.index_params.dim = self.model.no_components + return { + "features_col": "user_factors", + } + + @staticmethod + def unionify(df: DataFrame, df_2: Optional[DataFrame] = None) -> DataFrame: + if df_2 is not None: + df = df.unionByName(df_2) + return df + + def _check_parameters(self): + assert self.loss in self.losses, f"Only `{'`, `'.join(self.losses)}` losses can be used." + + def __init__( + self, + no_components: int = 10, + learning_schedule: str = "adagrad", + loss: str = "warp", + learning_rate: float = 0.05, + rho: float = 0.95, + epsilon: float = 1e-6, + k_kos_warp: int = 5, + n_kos_warp: int = 10, + max_sampled: int = 100, + random_state: Optional[int] = None, + tcp_port: int = 21235, + connection_timeout: int = 200, + index_builder: Optional[IndexBuilder] = None, + num_epochs: int = 30, + pygloo_timeout_sec: int = 200, + ): + """ + :param no_components: the dimensionality of the feature latent embeddings + :param learning_schedule: learning schedule used on training. One of (‘adagrad’, ‘adadelta’) + :param loss: loss function. One of (‘logistic’, ‘bpr’, ‘warp’, ‘warp-kos’) + :param learning_rate: initial learning rate for the adagrad learning schedule + :param rho: moving average coefficient for the adadelta learning schedule + :param epsilon: conditioning parameter for the adadelta learning schedule + :param k_kos_warp: for k-OS training, the k-th positive example will be selected from the n positive examples + sampled for every user + :param n_kos_warp: for k-OS training, maximum number of positives sampled for each update + :param max_sampled: maximum number of negative samples used during WARP fitting + :param random_state: the random seed to use when shuffling the data and initializing the parameters + :param tcp_port: port of TCP store used in collective communications + :param connection_timeout: TCP store connection timeout in seconds + :param index_builder: IndexBuilder instance for ANN predictions + :param num_epochs: number of training epochs + :param pygloo_timeout_sec: collective operations timeout in seconds + """ + if loss == "logistic": + self.logger.warning( + "Usage of distributed `logistic` loss can be unstable in performance." + ) + + self.no_components = no_components + self.learning_schedule = learning_schedule + self.loss = loss + self.learning_rate = learning_rate + self.rho = rho + self.epsilon = epsilon + self.k_kos_warp = k_kos_warp + self.n_kos_warp = n_kos_warp + self.max_sampled = max_sampled + self.random_state = random_state + self.tcp_port = tcp_port + self.connection_timeout = connection_timeout + + if isinstance(index_builder, (IndexBuilder, type(None))): + self.index_builder = index_builder + elif isinstance(index_builder, dict): + self.init_builder_from_dict(index_builder) + self._pygloo_timeout_sec = pygloo_timeout_sec + self._num_epochs = num_epochs + + self.model: Optional[LightFM] = None + self.world_size: Optional[int] = None + self.max_seen_user_idx: Optional[int] = None + self.max_seen_item_idx: Optional[int] = None + self.num_elements: Optional[int] = None + self.num_threads: Optional[int] = None + self.user_feat_scaler: Optional[MinMaxScaler] = None + self.item_feat_scaler: Optional[MinMaxScaler] = None + + self._check_parameters() + + @property + def num_epochs(self): + """ Get number of training epochs.""" + return self._num_epochs + + @num_epochs.setter + def num_epochs(self, value): + if value <= 0: + raise ValueError( + "Defined number of training epochs must be positive." + ) + self._num_epochs = value + + @property + def pygloo_timeout_sec(self): + """ Get timeout value in GLOO collective operations.""" + return self._pygloo_timeout_sec + + @pygloo_timeout_sec.setter + def pygloo_timeout_sec(self, value): + if value <= 0: + raise ValueError("Timout should be longer than 0 seconds.") + self._pygloo_timeout_sec = value + + def _save_model(self, path: str): + for weight in self.model_weights: + value_to_save = self.model.__getattribute__(weight) + self.model.__setattr__(weight, None) + save_picklable_to_parquet(value_to_save, join(path, weight)) + + save_picklable_to_parquet(self.model, join(path, "LightFM_model")) + save_picklable_to_parquet( + self.user_feat_scaler, join(path, "user_feat_scaler") + ) + save_picklable_to_parquet( + self.item_feat_scaler, join(path, "item_feat_scaler") + ) + + if self._use_ann: + self._save_index(path) + + def _load_model(self, path: str): + self.model = load_pickled_from_parquet(join(path, "LightFM_model")) + + for weight in self.model_weights: + self.model.__setattr__( + weight, load_pickled_from_parquet(join(path, weight)) + ) + + self.user_feat_scaler = load_pickled_from_parquet( + join(path, "user_feat_scaler") + ) + self.item_feat_scaler = load_pickled_from_parquet( + join(path, "item_feat_scaler") + ) + + if self._use_ann: + self._load_index(path) + + @property + def _init_args(self) -> dict: + return { + "no_components": self.no_components, + "learning_schedule": self.learning_schedule, + "loss": self.loss, + "learning_rate": self.learning_rate, + "rho": self.rho, + "epsilon": self.epsilon, + "k_kos_warp": self.k_kos_warp, + "n_kos_warp": self.n_kos_warp, + "max_sampled": self.max_sampled, + "random_state": self.random_state, + "tcp_port": self.tcp_port, + "connection_timeout": self.connection_timeout, + "index_builder": self.index_builder.init_meta_as_dict() if self.index_builder else None, + "num_epochs": self._num_epochs, + "pygloo_timeout_sec": self._pygloo_timeout_sec, + } + + def _initialize_world_size_and_threads(self): + # Get configurations parameters for Gloo workers + spark_sess = SparkSession.getActiveSession() + master_addr = spark_sess.conf.get("spark.master") + if master_addr.startswith("local-cluster"): + exec_str, cores_str, _ = master_addr[len("local-cluster["): -1].split(",") + num_executor_instances = int(exec_str) + num_executor_cores = int(cores_str) + elif master_addr.startswith("local"): + cores_str = master_addr[len("local["): -1] + num_executor_instances = 1 + num_executor_cores = (int(cores_str) if cores_str != "*" else mp.cpu_count()) + else: + num_executor_instances = int(spark_sess.conf.get("spark.executor.instances", "1")) + num_executor_cores = int(spark_sess.conf.get("spark.executor.cores", "1")) + self.world_size = max(num_executor_instances, 1) + self.num_threads = max(num_executor_cores, 1) + + def _initialize_and_reset_model_state_and_scalers(self): + # Reset user / item scalers in case if model was initialized earlier + self.user_feat_scaler = None + self.item_feat_scaler = None + # Initialize / reset model states in case it was trained earlier + self.model = LightFM( + no_components=self.no_components, + learning_schedule=self.learning_schedule, + loss=self.loss, + learning_rate=self.learning_rate, + rho=self.rho, + epsilon=self.epsilon, + k=self.k_kos_warp, + n=self.n_kos_warp, + max_sampled=self.max_sampled, + random_state=self.random_state, + ) + + def _fit( + self, + log: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + ) -> None: + self._initialize_and_reset_model_state_and_scalers() + self._fit_partial(log, user_features, item_features) + + def _predict( + self, + log: DataFrame, + k: int, + users: DataFrame, + items: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + filter_seen_items: bool = True, + ) -> DataFrame: + + users = users.distinct() + items = items.distinct() + pairs = users.crossJoin(items) + predict = self._predict_selected_pairs(pairs, user_features, item_features) + return predict + + def _predict_selected_pairs( + self, + pairs: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + ) -> DataFrame: + + @sf.pandas_udf(FloatType()) + def predict_by_user(user_idx: pd.Series, item_idx: pd.Series) -> pd.Series: + return pd.Series(model.predict( + user_ids=user_idx.copy().to_numpy(), + item_ids=item_idx.copy().to_numpy(), + item_features=csr_item_features, + user_features=csr_user_features, + )) + + model = self.model + + if self.can_predict_cold_users and user_features is None: + raise ValueError("User features are missing for predict") + if self.can_predict_cold_items and item_features is None: + raise ValueError("Item features are missing for predict") + + csr_item_features = self._convert_features_to_csr( + pairs.select("item_idx").distinct(), item_features + ) + csr_user_features = self._convert_features_to_csr( + pairs.select("user_idx").distinct(), user_features + ) + + return pairs.withColumn('relevance', predict_by_user(sf.col("user_idx"), sf.col("item_idx"))) + + def _convert_features_to_csr( + self, entity_ids: DataFrame, features: Optional[DataFrame] = None + ) -> Optional[sp.csr_matrix]: + """ + Convert DataFrame with features to scipy csr_matrix format if provided. + + :param entity_ids: user/item_ids from log + :param features: user/item_features in DataFrame format + :return: np.float32 csr_matrix of shape [n_users, n_user_features], optional + Each row contains that user's weights over features. + """ + if features is None: + return None + + check_numeric(features) + + entity = self._get_entity_name(features) + num_seen_entities = self.__getattribute__(f"max_seen_{entity}_idx") + # Filter out features and entities not presented in log or features DataFrame + features = features.join(entity_ids, on=f"{entity}_idx", how="inner") + + max_passed_id = (entity_ids.agg({f"{entity}_idx": "max"}).first()[0] + 1) + # Get number of new entities, for which model has not been trained on + num_cold_entities = max(0, max_passed_id - self.__getattribute__(f"max_seen_{entity}_idx")) + sparse_features = sp.vstack( + [ + # Identity features matrix + sp.eye( + num_seen_entities, num_seen_entities + num_cold_entities + ), + # Empty features matrix + sp.csr_matrix( + np.zeros( + ( + num_cold_entities, + num_seen_entities + num_cold_entities, + ) + ) + ), + ] + ) + + # features to (num_seen_entities + num_cold_entities) x num_features + feature_names = [ + col_name + for col_name in features.columns + if col_name != f"{entity}_idx" + ] + features_np = ( + features.select(f"{entity}_idx", *feature_names) + .toPandas() + .to_numpy() + ) + feature_entity_ids = features_np[:, 0] + features_columns = features_np[:, 1:] + number_of_features = features_columns.shape[1] + # Scale down dense features + scaler_name = f"{entity}_feat_scaler" + # Check if scaler for entity was already initialized + if self.__getattribute__(scaler_name) is None: + if not features_columns.size: + raise ValueError(f"features for {entity}s from log are absent") + # Update entity scaler with fitted scaler + self.__setattr__(scaler_name, MinMaxScaler().fit(features_columns)) + + if features_columns.size: + features_dense = self.__getattribute__(scaler_name).transform( + features_columns + ) + + features = sp.csr_matrix( + ( + features_dense.ravel(), + ( + np.repeat(feature_entity_ids, number_of_features), + np.tile( + np.arange(number_of_features), + feature_entity_ids.shape[0], + ), + ), + ), + shape=( + num_seen_entities + num_cold_entities, + number_of_features, + ), + ) + else: + features = sp.csr_matrix( + (num_seen_entities + num_cold_entities, number_of_features) + ) + return sp.hstack([sparse_features, features]) + + def _get_num_users_and_items( + self, log: DataFrame, previous_log: Optional[DataFrame] = None + ) -> Tuple[int, int]: + if previous_log is not None: + previous_log = previous_log.select(log.columns) + interactions = self.unionify(log, previous_log) + num_users = interactions.agg({"user_idx": "max"}).first()[0] + num_items = interactions.agg({"item_idx": "max"}).first()[0] + return num_users + 1, num_items + 1 + + # pylint: disable=unused-argument + def _reinitialize_embeddings( + self, + log: DataFrame, + previous_log: DataFrame, + num_users: int, + num_items: int, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + ) -> None: + """ + Partial fit model states reinitialization to adjust previous and current training logs. + + :param log: training log + :param previous_log: previous training log + :param num_users: number of users in log + :param num_items: number of items in log + :param user_features: user features in csr_matrix format (num_users x num_user_features) + :param item_features: item features in csr_matrix format (num_items x num_item_features) + :return: + """ + # Get number of users and items presented in previous log + ( + num_users_previous_log, + num_items_previous_log, + ) = self._get_num_users_and_items(previous_log) + + # Get number of users and items added in log + new_users = num_users - num_users_previous_log + new_items = num_items - num_items_previous_log + + # Get number of non-I users` and items` features in previous log and log + num_user_extra_features_previous_log = ( + self.model.user_embeddings.shape[0] - num_users_previous_log + ) + num_item_extra_features_previous_log = ( + self.model.item_embeddings.shape[0] - num_items_previous_log + ) + + if ( + self.model.user_embeddings.shape[0] - num_user_extra_features_previous_log != num_users_previous_log + ) or ( + self.model.item_embeddings.shape[0] - num_item_extra_features_previous_log != num_items_previous_log + ): + raise ValueError( + "Number of user/items features in trained model is not equal " + "to number of users/items in previous log." + ) + # Get number of non-I features if entities features are passed + num_user_extra_features_log = user_features.shape[1] - num_users if user_features is not None else 0 + num_item_extra_features_log = item_features.shape[1] - num_items if item_features is not None else 0 + + if not ( + num_user_extra_features_previous_log == num_user_extra_features_log + and num_item_extra_features_previous_log + == num_item_extra_features_log + ): + raise ValueError( + f"Passed number of extra features for user: {num_user_extra_features_log} \n" + f"Passed number of extra features for item: {num_item_extra_features_log}. \n" + f"But model was trained on {num_user_extra_features_previous_log} user and " + f"{num_item_extra_features_previous_log} item features." + ) + + # Initialise new item features. + new_item_embeddings = ( + (self.model.random_state.rand(new_items, self.no_components) - 0.5) + / self.no_components + ).astype(np.float32) + new_item_embedding_gradients = np.zeros_like(new_item_embeddings) + new_item_embedding_momentum = np.zeros_like(new_item_embeddings) + new_item_biases = np.zeros(new_items, dtype=np.float32) + new_item_bias_gradients = np.zeros_like(new_item_biases) + new_item_bias_momentum = np.zeros_like(new_item_biases) + + # Initialise new user features. + new_user_embeddings = ( + (self.model.random_state.rand(new_users, self.no_components) - 0.5) + / self.no_components + ).astype(np.float32) + new_user_embedding_gradients = np.zeros_like(new_user_embeddings) + new_user_embedding_momentum = np.zeros_like(new_user_embeddings) + new_user_biases = np.zeros(new_users, dtype=np.float32) + new_user_bias_gradients = np.zeros_like(new_user_biases) + new_user_bias_momentum = np.zeros_like(new_user_biases) + + def _concat_arrays(model_array: np.ndarray, added_array: np.ndarray, dissect_at: int): + return np.concatenate( + ( + model_array[:dissect_at], + added_array, + model_array[dissect_at:], + ) + ) + + def _update_values(entity_updates: List[Tuple[str, np.ndarray]], num_entities_previous_log: int) -> None: + for attribute_name, value in entity_updates: + # Concatenate new and old features preserving the identity and non identity features order + updated_value = _concat_arrays( + self.model.__getattribute__(attribute_name), + value, + num_entities_previous_log, + ) + self.model.__setattr__(attribute_name, updated_value) + + item_updates = [ + ("item_embeddings", new_item_embeddings), + ("item_embedding_gradients", new_item_embedding_gradients), + ("item_embedding_momentum", new_item_embedding_momentum), + ("item_biases", new_item_biases), + ("item_bias_gradients", new_item_bias_gradients), + ("item_bias_momentum", new_item_bias_momentum), + ] + user_updates = [ + ("user_embeddings", new_user_embeddings), + ("user_embedding_gradients", new_user_embedding_gradients), + ("user_embedding_momentum", new_user_embedding_momentum), + ("user_biases", new_user_biases), + ("user_bias_gradients", new_user_bias_gradients), + ("user_bias_momentum", new_user_bias_momentum), + ] + + _update_values(item_updates, num_items_previous_log) + _update_values(user_updates, num_users_previous_log) + + # pylint: disable=unused-variable, too-many-statements + def _fit_partial( + self, + log: DataFrame, + user_features: Optional[DataFrame] = None, + item_features: Optional[DataFrame] = None, + previous_log: Optional[DataFrame] = None, + ) -> None: + if self.model is None: + # If fit partial called on non initialized model + self._initialize_and_reset_model_state_and_scalers() + self._initialize_world_size_and_threads() + + self.can_predict_cold_users = user_features is not None + self.can_predict_cold_items = item_features is not None + + # Prepare interactions log + log = log.select("user_idx", "item_idx", sf.lit(1).alias("relevance")) + log = log.repartition(self.world_size, "user_idx") + + n_users, n_items = self._get_num_users_and_items( + log=log, previous_log=previous_log + ) + # Update value of maximum seen users and items number + self.max_seen_user_idx = n_users + self.max_seen_item_idx = n_items + user_features = self._convert_features_to_csr(log.select("user_idx").distinct(), user_features) + item_features = self._convert_features_to_csr(log.select("item_idx").distinct(), item_features) + # Update model`s embeddings and biases if model was initialized and trained on previous log + if previous_log is not None: + if self.model.item_embeddings is not None: + self._reinitialize_embeddings( + log, + previous_log, + num_users=n_users, + num_items=n_items, + user_features=user_features, + item_features=item_features, + ) + else: + raise ValueError( + "Passed previous log will not affect training " + "as the model has not been trained yet." + ) + + ( + user_features, + item_features, + ) = self.model._construct_feature_matrices(n_users, n_items, user_features, item_features) + + for input_data in ( + user_features.data, + item_features.data, + ): + self.model._check_input_finite(input_data) + + if self.model.item_embeddings is None: + self.model._initialize( + self.model.no_components, + item_features.shape[1], + user_features.shape[1], + ) + + if not item_features.shape[1] == self.model.item_embeddings.shape[0]: + raise ValueError("Incorrect number of features in item_features") + if not user_features.shape[1] == self.model.user_embeddings.shape[0]: + raise ValueError("Incorrect number of features in user_features") + if self.num_threads < 1: + raise ValueError("Number of threads must be 1 or larger.") + # Initialize trainer instance to perform distributed training using Gloo workers + training_instance = LightFMTraining(self.model, self.world_size, self.num_threads) + # Initialize TCP store for collective communications + host = training_instance.get_host_ip() + real_store = dist.TCPStore( + host, + self.tcp_port, + self.world_size + 1, + True, + timedelta(seconds=self.connection_timeout), + ) + + _tcp_port = self.tcp_port + _connection_timeout = self.connection_timeout + _num_epochs = self._num_epochs + _pygloo_timeout = self._pygloo_timeout_sec + + def udf_to_map_on_interactions_with_index( + p_idx, partition_interactions + ): + # Initialize pygloo context + context = pgl.rendezvous.Context(p_idx, training_instance.world_size) + local_ip = training_instance.get_host_ip() + attr = pgl.transport.tcp.attr(local_ip) + dev = pgl.transport.tcp.CreateDevice(attr) + local_store = dist.TCPStore( + host, + _tcp_port, + training_instance.world_size + 1, + False, + timedelta(seconds=_connection_timeout), + ) + store = pgl.rendezvous.CustomStore(local_store) + context.setTimeout(timedelta(seconds=_pygloo_timeout)) + context.connectFullMesh(store, dev) + gloo_context = context + + # Prapare data + interactions = training_instance.rdd_to_csr(partition_interactions, num_users=n_users, num_items=n_items) + interactions = interactions.tocoo() + + if interactions.dtype != np.float32: + interactions.data = interactions.data.astype(np.float32) + + # Initialize sample weights + sample_weight_data = training_instance.model._process_sample_weight( + interactions, sample_weight=None + ) + for input_data in ( + interactions.data, + sample_weight_data, + ): + training_instance.model._check_input_finite(input_data) + + # Get local interactions partition in COO sparse matrix format + interactions_part = sp.coo_matrix( + (interactions.data, (interactions.row, interactions.col)), + shape=(n_users, n_items), + ) + + # Copy model states to executors + local_training_features = ( + training_instance.initialize_local_state() + ) + + # Each Spark executor runs on interaction matrix partition + for _epoch_num in training_instance.model._progress(_num_epochs, verbose=False): + training_instance.run_epoch_spark( + item_features, + user_features, + interactions_part, + sample_weight_data, + gloo_context, + local_training_features, + ) + training_instance.model._check_finite() + + if p_idx == 0: + yield training_instance.model + + self.model = log.rdd.mapPartitionsWithIndex(udf_to_map_on_interactions_with_index).first() diff --git a/replay/models/distributed_lightfm_wrap/utils.py b/replay/models/distributed_lightfm_wrap/utils.py new file mode 100644 index 000000000..8361106e7 --- /dev/null +++ b/replay/models/distributed_lightfm_wrap/utils.py @@ -0,0 +1,369 @@ +import itertools +import socket +from dataclasses import dataclass +from typing import Tuple, Union + +import numpy as np +from lightfm import LightFM +from lightfm._lightfm_fast import ( + CSRMatrix, + FastLightFM, + fit_bpr, + fit_logistic, + fit_warp, + fit_warp_kos, +) +import pygloo as pgl + +from scipy.sparse import csr_matrix + + +@dataclass +class ModelFeatures: + """ Model representations. """ + + item_features: np.ndarray + item_biases: np.ndarray + user_features: np.ndarray + user_biases: np.ndarray + + +# pylint: disable=too-many-instance-attributes, unnecessary-dunder-call +@dataclass +class ModelTrainingFeatures: + """ Model features used on training. """ + + item_feature_gradients: np.ndarray + item_feature_momentum: np.ndarray + item_bias_gradients: np.ndarray + item_bias_momentum: np.ndarray + user_feature_gradients: np.ndarray + user_feature_momentum: np.ndarray + user_bias_gradients: np.ndarray + user_bias_momentum: np.ndarray + + user_alpha: np.ndarray + item_alpha: np.ndarray + + +# pylint: disable=too-many-instance-attributes, too-many-arguments +class LightFMTraining: + """LightFMTraining class used in distributed LightFM to perform training steps and results synchronization. + + To perform a LightFM model training in a distributed fashion, each Spark executor is initialized as a Gloo worker. + The interation matrix is divided between executors. Additionally, OpenMP threads are initialized according to + the number of Spark executor cores. + Before the training begins, interaction matrix is partitioned by `user_idx`, model states (item and user embeddings + and biases, along with training features) are copied to the Spark executors. Then, the Gloo context is initialized, + each executor is registered as the Gloo worker, each of which will train the model on the part of the interaction + matrix. + During the training, on each epoch, model`s weights and user/item representations updates are performed separately + by Gloo workers. At the end of each epoch, worker collects the updates and AllReduce collective operation is + performed to synchronize the results with other Gloo workers. + + :param model: LightFM model instance + :param world_size: collective communication world size - number of Gloo workers + :param num_threads: number of threads for OpenMP parallelization within an executor + """ + + def __init__(self, model: LightFM, world_size: int, num_threads: int): + self.model = model + self.world_size = world_size + self.num_threads = num_threads + + def initialize_local_state(self) -> ModelTrainingFeatures: + """ Create local copy of the model states. """ + return ModelTrainingFeatures( + item_feature_gradients=self.model.item_embedding_gradients.copy(), + item_feature_momentum=self.model.item_embedding_momentum.copy(), + item_bias_gradients=self.model.item_bias_gradients.copy(), + item_bias_momentum=self.model.item_bias_momentum.copy(), + user_feature_gradients=self.model.user_embedding_gradients.copy(), + user_feature_momentum=self.model.user_embedding_momentum.copy(), + user_bias_gradients=self.model.user_bias_gradients.copy(), + user_bias_momentum=self.model.user_bias_momentum.copy(), + user_alpha=self.model.user_alpha, + item_alpha=self.model.item_alpha, + ) + + @staticmethod + def rdd_to_csr( + partition_interactions: itertools.chain, num_users: int, num_items: int + ): + """ Convert Spark RDD into scipy CSR matrix.""" + user_ids, item_ids, relevance = [], [], [] + for row in partition_interactions: + user_ids.append(row.user_idx) + item_ids.append(row.item_idx) + relevance.append(row.relevance) + + csr = csr_matrix( + (relevance, (user_ids, item_ids)), shape=(num_users, num_items), + ) + return csr + + @staticmethod + def get_host_ip(): + """ Get the IP of the host. """ + hostname = socket.gethostname() + ip_address = socket.gethostbyname(hostname) + return ip_address + + def _copy_representations_for_update(self) -> ModelFeatures: + """ Create local copy of the item and user representations. """ + return ModelFeatures( + item_features=self.model.item_embeddings.copy(), + item_biases=self.model.item_biases.copy(), + user_features=self.model.user_embeddings.copy(), + user_biases=self.model.user_biases.copy(), + ) + + def _get_lightfm_data( + self, features: ModelFeatures, training_features: ModelTrainingFeatures + ) -> FastLightFM: + """ Create FastLightFM class from the states to run update. """ + + lightfm_data = FastLightFM( + features.item_features, + training_features.item_feature_gradients, + training_features.item_feature_momentum, + features.item_biases, + training_features.item_bias_gradients, + training_features.item_bias_momentum, + features.user_features, + training_features.user_feature_gradients, + training_features.user_feature_momentum, + features.user_biases, + training_features.user_bias_gradients, + training_features.user_bias_momentum, + self.model.no_components, + int(self.model.learning_schedule == "adadelta"), + self.model.learning_rate, + self.model.rho, + self.model.epsilon, + self.model.max_sampled, + ) + + return lightfm_data + + def run_epoch_spark( + self, + item_features, + user_features, + interactions, + sample_weight, + gloo_context, + training_features: ModelTrainingFeatures, + ): + """ Run an epoch of training. """ + if self.model.loss in ("warp", "bpr", "warp-kos",): + positives_lookup = CSRMatrix( + self.model._get_positives_lookup_matrix(interactions) + ) + + shuffle_indices = np.arange(len(interactions.data), dtype=np.int32) + self.model.random_state.shuffle(shuffle_indices) + + # Get representations copies from the local model + local_features = self._copy_representations_for_update() + lightfm_data = self._get_lightfm_data( + features=local_features, training_features=training_features + ) + + if self.model.loss == "warp": + # Run updates on the model state copy + fit_warp( + CSRMatrix(item_features), + CSRMatrix(user_features), + positives_lookup, + interactions.row, + interactions.col, + interactions.data, + sample_weight, + shuffle_indices, + lightfm_data, + self.model.learning_rate, + training_features.item_alpha, + training_features.user_alpha, + self.num_threads, + self.model.random_state, + ) + elif self.model.loss == "bpr": + fit_bpr( + CSRMatrix(item_features), + CSRMatrix(user_features), + positives_lookup, + interactions.row, + interactions.col, + interactions.data, + sample_weight, + shuffle_indices, + lightfm_data, + self.model.learning_rate, + training_features.item_alpha, + training_features.user_alpha, + self.num_threads, + self.model.random_state, + ) + elif self.model.loss == "warp-kos": + fit_warp_kos( + CSRMatrix(item_features), + CSRMatrix(user_features), + positives_lookup, + interactions.row, + shuffle_indices, + lightfm_data, + self.model.learning_rate, + training_features.item_alpha, + training_features.user_alpha, + self.model.k, + self.model.n, + self.num_threads, + self.model.random_state, + ) + elif self.model.loss == "logistic": + fit_logistic( + CSRMatrix(item_features), + CSRMatrix(user_features), + interactions.row, + interactions.col, + interactions.data, + sample_weight, + shuffle_indices, + lightfm_data, + self.model.learning_rate, + training_features.item_alpha, + training_features.user_alpha, + self.num_threads, + ) + else: + raise NotImplementedError( + "Only `warp`, `bpr` losses are available by the moment" + ) + + # Get embeddings deltas before reduction + local_features = self._get_update_delta_after_fit(local_features) + # Perform AllReduce reduction on local states + local_features, training_features = self._reduce_states_on_workers( + gloo_context, local_features, training_features + ) + # Update local models with common model states + self._update_model_with_reduced_data(local_features, training_features) + + def _get_update_delta_after_fit( + self, local_features: ModelFeatures + ) -> ModelFeatures: + """ Extract initial representation values to get delta from update. """ + local_features.item_features -= self.model.item_embeddings + local_features.item_biases -= self.model.item_biases + local_features.user_features -= self.model.user_embeddings + local_features.user_biases -= self.model.user_biases + return local_features + + def _reduce_states_on_workers( + self, + gloo_context, + features: ModelFeatures, + training_features: ModelTrainingFeatures, + ) -> Tuple[ModelFeatures, ModelTrainingFeatures]: + """ Perform AllReduce operation on representations and optimization parameters. """ + + def reduce_( + item: Union[ModelTrainingFeatures, ModelFeatures], + attr_name: str, + operation: str = "sum", + ) -> Union[ModelTrainingFeatures, ModelFeatures]: + sendbuf = item.__getattribute__(attr_name) + recvbuf = np.zeros_like(sendbuf, dtype=np.float32) + sendptr = sendbuf.ctypes.data + recvptr = recvbuf.ctypes.data + data_size = ( + sendbuf.size + if isinstance(sendbuf, np.ndarray) + else sendbuf.numpy().size + ) + datatype = pgl.glooDataType_t.glooFloat32 + + pgl.allreduce( + gloo_context, + sendptr, + recvptr, + data_size, + datatype, + pgl.ReduceOp.SUM, + pgl.allreduceAlgorithm.RING, + ) + + item.__setattr__(attr_name, recvbuf) + if operation == "avg": + attr_value = item.__getattribute__(attr_name) + item.__setattr__(attr_name, attr_value / self.world_size) + + return item + + avg_attributes_features = ( + "item_features", + "item_biases", + ) + sum_attributes_features = ( + "user_features", + "user_biases", + ) + avg_attributes_training_features = ( + "item_feature_gradients", + "item_feature_momentum", + "item_bias_gradients", + "item_bias_momentum", + "user_feature_gradients", + "user_feature_momentum", + "user_bias_gradients", + "user_bias_momentum", + ) + + for sum_attributes_feat in sum_attributes_features: + features = reduce_( + features, attr_name=sum_attributes_feat, operation="sum" + ) + + for avg_attributes_feat in avg_attributes_features: + features = reduce_( + features, attr_name=avg_attributes_feat, operation="avg" + ) + + for avg_attributes_tr_feat in avg_attributes_training_features: + training_features = reduce_( + training_features, + attr_name=avg_attributes_tr_feat, + operation="avg", + ) + + return features, training_features + + def _update_model_with_reduced_data( + self, + features: ModelFeatures, + training_features: ModelTrainingFeatures, + ): + """ Updates model state after parallel operations. """ + + self.model.item_embeddings += features.item_features + self.model.item_embedding_gradients = ( + training_features.item_feature_gradients + ) + self.model.item_embedding_momentum = ( + training_features.item_feature_momentum + ) + self.model.item_biases += features.item_biases + self.model.item_bias_gradients = training_features.item_bias_gradients + self.model.item_bias_momentum = training_features.item_bias_momentum + + self.model.user_embeddings += features.user_features + self.model.user_embedding_gradients = ( + training_features.user_feature_gradients + ) + self.model.user_embedding_momentum = ( + training_features.user_feature_momentum + ) + self.model.user_biases += features.user_biases + self.model.user_bias_gradients = training_features.user_bias_gradients + self.model.user_bias_momentum = training_features.user_bias_momentum diff --git a/replay/models/knn.py b/replay/models/knn.py index da6675af8..2135cfffb 100644 --- a/replay/models/knn.py +++ b/replay/models/knn.py @@ -1,16 +1,23 @@ -from typing import Optional +from typing import Optional, Dict, Any from pyspark.sql import DataFrame from pyspark.sql import functions as sf from pyspark.sql.window import Window -from replay.models.base_rec import NeighbourRec +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.models.base_neighbour_rec import NeighbourRec from replay.optuna_objective import ItemKNNObjective +# pylint: disable=too-many-ancestors class ItemKNN(NeighbourRec): """Item-based ItemKNN with modified cosine similarity measure.""" + def _get_ann_infer_params(self) -> Dict[str, Any]: + return { + "features_col": None, + } + all_items: Optional[DataFrame] dot_products: Optional[DataFrame] item_norms: Optional[DataFrame] @@ -23,18 +30,21 @@ class ItemKNN(NeighbourRec): "weighting": {"type": "categorical", "args": [None, "tf_idf", "bm25"]} } - def __init__( + def __init__( # pylint: disable=too-many-arguments self, num_neighbours: int = 10, use_relevance: bool = False, shrink: float = 0.0, weighting: str = None, + index_builder: Optional[IndexBuilder] = None, ): """ :param num_neighbours: number of neighbours :param use_relevance: flag to use relevance values as is or to treat them as 1 :param shrink: term added to the denominator when calculating similarity :param weighting: item reweighting type, one of [None, 'tf_idf', 'bm25'] + :param index_builder: `IndexBuilder` instance that adds ANN functionality. + If not set, then ann will not be used. """ self.shrink = shrink self.use_relevance = use_relevance @@ -44,6 +54,10 @@ def __init__( if weighting not in valid_weightings: raise ValueError(f"weighting must be one of {valid_weightings}") self.weighting = weighting + if isinstance(index_builder, (IndexBuilder, type(None))): + self.index_builder = index_builder + elif isinstance(index_builder, dict): + self.init_builder_from_dict(index_builder) @property def _init_args(self): @@ -52,8 +66,17 @@ def _init_args(self): "use_relevance": self.use_relevance, "num_neighbours": self.num_neighbours, "weighting": self.weighting, + "index_builder": self.index_builder.init_meta_as_dict() if self.index_builder else None, } + def _save_model(self, path: str): + if self._use_ann: + self._save_index(path) + + def _load_model(self, path: str): + if self._use_ann: + self._load_index(path) + @staticmethod def _shrink(dot_products: DataFrame, shrink: float) -> DataFrame: return dot_products.withColumn( diff --git a/replay/models/slim.py b/replay/models/slim.py index e304ea02e..5cce6d1dd 100644 --- a/replay/models/slim.py +++ b/replay/models/slim.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict, Any import numpy as np import pandas as pd @@ -7,14 +7,22 @@ from scipy.sparse import csc_matrix from sklearn.linear_model import ElasticNet -from replay.models.base_rec import NeighbourRec +from replay.ann.index_builders.base_index_builder import IndexBuilder +from replay.models.base_neighbour_rec import NeighbourRec from replay.session_handler import State +# pylint: disable=too-many-ancestors class SLIM(NeighbourRec): """`SLIM: Sparse Linear Methods for Top-N Recommender Systems `_""" + def _get_ann_infer_params(self) -> Dict[str, Any]: + return { + "features_col": None, + # "params": self._nmslib_hnsw_params, + } + _search_space = { "beta": {"type": "loguniform", "args": [1e-6, 5]}, "lambda_": {"type": "loguniform", "args": [1e-6, 2]}, @@ -25,21 +33,41 @@ def __init__( beta: float = 0.01, lambda_: float = 0.01, seed: Optional[int] = None, + index_builder: Optional[IndexBuilder] = None, ): """ :param beta: l2 regularization :param lambda_: l1 regularization :param seed: random seed + :param index_builder: `IndexBuilder` instance that adds ANN functionality. + If not set, then ann will not be used. """ if beta < 0 or lambda_ <= 0: raise ValueError("Invalid regularization parameters") self.beta = beta self.lambda_ = lambda_ self.seed = seed + if isinstance(index_builder, (IndexBuilder, type(None))): + self.index_builder = index_builder + elif isinstance(index_builder, dict): + self.init_builder_from_dict(index_builder) @property def _init_args(self): - return {"beta": self.beta, "lambda_": self.lambda_, "seed": self.seed} + return { + "beta": self.beta, + "lambda_": self.lambda_, + "seed": self.seed, + "index_builder": self.index_builder.init_meta_as_dict() if self.index_builder else None, + } + + def _save_model(self, path: str): + if self._use_ann: + self._save_index(path) + + def _load_model(self, path: str): + if self._use_ann: + self._load_index(path) def _fit( self, diff --git a/replay/models/word2vec.py b/replay/models/word2vec.py index 62c00fcf8..3113ea6c4 100644 --- a/replay/models/word2vec.py +++ b/replay/models/word2vec.py @@ -1,21 +1,58 @@ -from typing import Optional +from typing import Optional, Dict, Any from pyspark.ml.feature import Word2Vec +from pyspark.ml.functions import vector_to_array +from pyspark.ml.stat import Summarizer from pyspark.sql import DataFrame from pyspark.sql import functions as sf from pyspark.sql import types as st -from pyspark.ml.stat import Summarizer +from replay.ann.ann_mixin import ANNMixin +from replay.ann.index_builders.base_index_builder import IndexBuilder from replay.models.base_rec import Recommender, ItemVectorModel from replay.utils import vector_dot, multiply_scala_udf, join_with_col_renaming -# pylint: disable=too-many-instance-attributes -class Word2VecRec(Recommender, ItemVectorModel): +# pylint: disable=too-many-instance-attributes, too-many-ancestors +class Word2VecRec(Recommender, ItemVectorModel, ANNMixin): """ Trains word2vec model where items ar treated as words and users as sentences. """ + def _get_ann_infer_params(self) -> Dict[str, Any]: + self.index_builder.index_params.dim = self.rank + return { + "features_col": "user_vector", + } + + def _get_vectors_to_infer_ann_inner(self, log: DataFrame, users: DataFrame) -> DataFrame: + user_vectors = self._get_user_vectors(users, log) + # converts to pandas_udf compatible format + user_vectors = user_vectors.select( + "user_idx", vector_to_array("user_vector").alias("user_vector") + ) + return user_vectors + + def _get_ann_build_params(self, log: DataFrame) -> Dict[str, Any]: + self.index_builder.index_params.dim = self.rank + self.index_builder.index_params.max_elements = log.select("item_idx").distinct().count() + self.logger.debug("index 'num_elements' = %s", self.num_elements) + return { + "features_col": "item_vector", + "ids_col": "item_idx" + } + + def _get_vectors_to_build_ann(self, log: DataFrame) -> DataFrame: + item_vectors = self._get_item_vectors() + item_vectors = ( + item_vectors + .select( + "item_idx", + vector_to_array("item_vector").alias("item_vector") + ) + ) + return item_vectors + idf: DataFrame vectors: DataFrame @@ -37,6 +74,7 @@ def __init__( use_idf: bool = False, seed: Optional[int] = None, num_partitions: Optional[int] = None, + index_builder: Optional[IndexBuilder] = None, ): """ :param rank: embedding size @@ -47,6 +85,8 @@ def __init__( :param window_size: window size :param use_idf: flag to use inverse document frequency :param seed: random seed + :param index_builder: `IndexBuilder` instance that adds ANN functionality. + If not set, then ann will not be used. """ self.rank = rank @@ -57,6 +97,11 @@ def __init__( self.max_iter = max_iter self._seed = seed self._num_partitions = num_partitions + if isinstance(index_builder, (IndexBuilder, type(None))): + self.index_builder = index_builder + elif isinstance(index_builder, dict): + self.init_builder_from_dict(index_builder) + self.num_elements = None @property def _init_args(self): @@ -68,8 +113,26 @@ def _init_args(self): "step_size": self.step_size, "max_iter": self.max_iter, "seed": self._seed, + "index_builder": self.index_builder.init_meta_as_dict() if self.index_builder else None, } + def _save_model(self, path: str): + # # create directory on shared disk or in HDFS + # path_info = get_filesystem(path) + # destination_filesystem, target_dir_path = fs.FileSystem.from_uri( + # path_info.hdfs_uri + path_info.path + # if path_info.filesystem == FileSystem.HDFS + # else path_info.path + # ) + # destination_filesystem.create_dir(target_dir_path) + + if self.index_builder: + self._save_index(path) + + def _load_model(self, path: str): + if self.index_builder: + self._load_index(path) + def _fit( self, log: DataFrame, diff --git a/replay/scenarios/two_stages/two_stages_scenario.py b/replay/scenarios/two_stages/two_stages_scenario.py index 26816845d..d7f188029 100644 --- a/replay/scenarios/two_stages/two_stages_scenario.py +++ b/replay/scenarios/two_stages/two_stages_scenario.py @@ -90,7 +90,7 @@ def get_first_level_model_features( ) factors_to_explode.append(("item_factors", "if")) - if model.__str__() == "LightFMWrap": + if model.__str__() in ["LightFMWrap", "DistributedLightFMWrap"]: pairs_with_features = ( pairs_with_features.fillna({"user_bias": 0, "item_bias": 0}) .withColumnRenamed("user_bias", f"{prefix}_user_bias") diff --git a/replay/session_handler.py b/replay/session_handler.py index 8aa012bdd..f156f64fd 100644 --- a/replay/session_handler.py +++ b/replay/session_handler.py @@ -50,6 +50,7 @@ def get_spark_session( .config("spark.driver.host", "localhost") .config("spark.sql.execution.arrow.pyspark.enabled", "true") .config("spark.kryoserializer.buffer.max", "256m") + .config("spark.files.overwrite", "true") .master("local[*]") .enableHiveSupport() .getOrCreate() diff --git a/replay/utils.py b/replay/utils.py index e49994b2d..125ad4590 100644 --- a/replay/utils.py +++ b/replay/utils.py @@ -1,5 +1,7 @@ import collections import pickle +from dataclasses import dataclass +from enum import Enum from typing import Any, Iterable, List, Optional, Set, Tuple, Union import numpy as np @@ -396,7 +398,7 @@ def to_csr( return csr_matrix( ( [], - ([],[]), + ([], []), ), shape=(0, 0), ) @@ -739,6 +741,101 @@ def drop_temp_view(temp_view_name: str) -> None: spark.catalog.dropTempView(temp_view_name) +class FileSystem(Enum): + """File system types""" + HDFS = 1 + LOCAL = 2 + + +def get_default_fs() -> str: + """Returns hadoop `fs.defaultFS` property value""" + spark = SparkSession.getActiveSession() + hadoop_conf = spark._jsc.hadoopConfiguration() + default_fs = hadoop_conf.get("fs.defaultFS") + return default_fs + + +@dataclass(frozen=True) +class FileInfo: + """File meta-information: filesystem, path and hdfs_uri (optional)""" + path: str + filesystem: FileSystem + hdfs_uri: str = None + + +def get_filesystem(path: str) -> FileInfo: + """Analyzes path and hadoop config and return `FileInfo` instance with `filesystem`, + `hdfs uri` (if filesystem is hdfs) and `cleaned path` (without prefix). + + For example: + + >>> path = 'hdfs://node21.bdcl:9000/tmp/file' + >>> get_filesystem(path) + FileInfo(path='/tmp/file', filesystem=, hdfs_uri='hdfs://node21.bdcl:9000') + + >>> path = 'file:///tmp/file' + >>> get_filesystem(path) + FileInfo(path='/tmp/file', filesystem=, hdfs_uri=None) + + >>> spark = SparkSession.builder.master("local[1]").getOrCreate() + >>> path = 'hdfs:///tmp/file' + >>> get_filesystem(path) + Traceback (most recent call last): + ... + ValueError: Can't get default hdfs uri for path = 'hdfs:///tmp/file'. \ +Specify an explicit path, such as 'hdfs://host:port/dir/file', \ +or set 'fs.defaultFS' in hadoop configuration. + + >>> path = '/tmp/file' + >>> get_filesystem(path) + FileInfo(path='/tmp/file', filesystem=, hdfs_uri=None) + + >>> spark = SparkSession.builder.master("local[1]").getOrCreate().newSession() + >>> spark.sparkContext._jsc.hadoopConfiguration().set('fs.defaultFS', 'hdfs://node21.bdcl:9000') + >>> path = '/tmp/file' + >>> get_filesystem(path) + FileInfo(path='/tmp/file', filesystem=, hdfs_uri='hdfs://node21.bdcl:9000') + + >>> path = 'hdfs:///tmp/file' + >>> get_filesystem(path) + FileInfo(path='/tmp/file', filesystem=, hdfs_uri='hdfs://node21.bdcl:9000') + + Return fs.defaultFS value because the current session may be used in another test + >>> spark.sparkContext._jsc.hadoopConfiguration().set('fs.defaultFS', 'file:///') + + + Args: + path (str): path to file on hdfs or local disk + + Returns: FileInfo instance: `filesystem id`, + `hdfs uri` (if filesystem is hdfs) and `cleaned path` (without prefix) + """ + prefix_len = 7 # 'hdfs://' and 'file://' length + if path.startswith("hdfs://"): + if path.startswith("hdfs:///"): + default_fs = get_default_fs() + if default_fs.startswith("hdfs://"): + return FileInfo(path[prefix_len:], FileSystem.HDFS, default_fs) + else: + raise ValueError( + f"Can't get default hdfs uri for path = '{path}'. " + "Specify an explicit path, such as 'hdfs://host:port/dir/file', " + "or set 'fs.defaultFS' in hadoop configuration." + ) + else: + hostname = path[prefix_len:].split("/", 1)[0] + hdfs_uri = "hdfs://" + hostname + return FileInfo(path[len(hdfs_uri) :], FileSystem.HDFS, hdfs_uri) + elif path.startswith("file://"): + return FileInfo(path[prefix_len:], FileSystem.LOCAL) + else: + default_fs = get_default_fs() + if default_fs.startswith("hdfs://"): + return FileInfo(path, FileSystem.HDFS, default_fs) + else: + return FileInfo(path, FileSystem.LOCAL) + + def sample_top_k_recs(pairs: DataFrame, k: int, seed: int = None): """ Sample k items for each user with probability proportional to the relevance score. diff --git a/tests/models/test_als.py b/tests/models/test_als.py index 67da10cab..5532a2716 100644 --- a/tests/models/test_als.py +++ b/tests/models/test_als.py @@ -4,6 +4,13 @@ from pyspark.sql import functions as sf +from replay.ann.entities.hnswlib_param import HnswlibParam +from replay.ann.index_builders.executor_hnswlib_index_builder import ( + ExecutorHnswlibIndexBuilder, +) +from replay.ann.index_stores.shared_disk_index_store import ( + SharedDiskIndexStore, +) from replay.models import ALSWrap from replay.scenarios.two_stages.two_stages_scenario import ( get_first_level_model_features, @@ -18,6 +25,28 @@ def model(): return model +@pytest.fixture +def model_with_ann(tmp_path): + model = ALSWrap( + rank=2, + implicit_prefs=False, + seed=42, + index_builder=ExecutorHnswlibIndexBuilder( + index_params=HnswlibParam( + space="ip", + m=100, + ef_c=2000, + post=0, + ef_s=2000, + ), + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), index_dir="hnswlib_index" + ), + ), + ) + return model + + def test_works(log, model): try: pred = model.fit_predict(log, k=1) @@ -65,3 +94,23 @@ def test_enrich_with_features(log, model): [row_dict["_if_1"], row_dict["_if_1"] * row_dict["_uf_1"]], [-2.938199281692505, 0], ) + + +@pytest.mark.parametrize( + "filter_seen_items", [True, False] +) +def test_ann_predict(log, model, model_with_ann, filter_seen_items): + model.fit(log) + recs1 = model.predict(log, k=1, filter_seen_items=filter_seen_items) + + model_with_ann.fit(log) + recs2 = model_with_ann.predict(log, k=1, filter_seen_items=filter_seen_items) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert recs1.user_idx.equals(recs2.user_idx) + assert recs1.item_idx.equals(recs2.item_idx) diff --git a/tests/models/test_distributed_lightfm_wrap.py b/tests/models/test_distributed_lightfm_wrap.py new file mode 100644 index 000000000..5e5419115 --- /dev/null +++ b/tests/models/test_distributed_lightfm_wrap.py @@ -0,0 +1,225 @@ +# pylint: disable-all +from datetime import datetime + +import numpy as np +import pytest +from pyspark.sql import functions as sf + +from replay.ann.index_builders.driver_hnswlib_index_builder import DriverHnswlibIndexBuilder +from replay.ann.index_stores.spark_files_index_store import SparkFilesIndexStore +from replay.constants import LOG_SCHEMA +from replay.models import DistributedLightFMWrap +from replay.scenarios.two_stages.two_stages_scenario import ( + get_first_level_model_features, +) +from replay.ann.entities.hnswlib_param import HnswlibParam + +from tests.utils import spark, log as log_hnswlib_pred + + +@pytest.fixture +def log(spark): + date = datetime(2019, 1, 1) + return spark.createDataFrame( + data=[ + [0, 0, date, 1.0], + [1, 0, date, 1.0], + [2, 1, date, 2.0], + [2, 1, date, 2.0], + [1, 1, date, 2.0], + [2, 2, date, 2.0], + [0, 2, date, 2.0], + ], + schema=LOG_SCHEMA, + ) + + +@pytest.fixture +def user_features(spark): + return spark.createDataFrame( + [(0, 2.0, 5.0), (1, 0.0, -5.0), (4, 4.0, 3.0)] + ).toDF("user_idx", "user_feature_1", "user_feature_2") + + +@pytest.fixture +def item_features(spark): + return spark.createDataFrame([(0, 4.0, 5.0), (1, 5.0, 4.0)]).toDF( + "item_idx", "item_feature_1", "item_feature_2" + ) + + +@pytest.fixture +def model(): + model = DistributedLightFMWrap( + no_components=1, + random_state=42, + loss="bpr", + tcp_port=1234, + connection_timeout=10, + ) + model.num_epochs = 10 + return model + + +@pytest.fixture +def model_ann(): + model = DistributedLightFMWrap( + no_components=1, + random_state=42, + loss="bpr", + tcp_port=1234, + connection_timeout=10, + index_builder=DriverHnswlibIndexBuilder( + index_params=HnswlibParam( + space="ip", + m=100, + ef_c=2000, + post=0, + ef_s=2000, + ), + index_store=SparkFilesIndexStore(), + ), + ) + model.num_epochs = 10 + return model + + +def test_predict(log, user_features, item_features, model): + model.fit(log, user_features, item_features) + pred = model.predict( + log=log, + k=1, + user_features=user_features, + item_features=item_features, + filter_seen_items=True, + ) + + assert list(pred.toPandas().sort_values("user_idx")["item_idx"]) == [1, 2, 0] + + +def test_predict_ann(log_hnswlib_pred, user_features, item_features, model_ann): + model_ann.fit(log_hnswlib_pred, user_features, item_features) + pred = model_ann.predict( + log=log_hnswlib_pred, + k=1, + user_features=user_features, + item_features=item_features, + filter_seen_items=True, + ) + + assert list(pred.toPandas().sort_values("user_idx")["item_idx"]) == [3, 1, 3, 3] + + +def test_predict_no_user_features(log, user_features, item_features, model): + model.fit(log, None, item_features) + assert model.can_predict_cold_items + assert not model.can_predict_cold_users + pred = model.predict( + log=log, + k=1, + user_features=None, + item_features=item_features, + filter_seen_items=True, + ) + assert list(pred.toPandas().sort_values("user_idx")["item_idx"]) == [1, 2, 0] + + +def test_predict_pairs(log, user_features, item_features, model): + try: + model.fit( + log.filter(sf.col("user_idx") != 0), + user_features.filter(sf.col("user_idx") != 0), + item_features, + ) + pred = model.predict_pairs( + log.filter(sf.col("user_idx") == 0).select("user_idx", "item_idx"), + user_features=user_features, + item_features=item_features, + ) + assert pred.count() == 2 + assert pred.select("user_idx").distinct().collect()[0][0] == 0 + pred = model.predict_pairs( + log.filter(sf.col("user_idx") == 1).select("user_idx", "item_idx"), + user_features=user_features, + item_features=item_features, + ) + assert pred.count() == 2 + assert pred.select("user_idx").distinct().collect()[0][0] == 1 + except: # noqa + pytest.fail() + + +def test_raises_fit(log, user_features, item_features, model): + with pytest.raises(ValueError, match="features for .*"): + model.fit( + log.filter(sf.col("user_idx") != 0), + user_features.filter(sf.col("user_idx") != 1), + item_features, + ) + + +def test_raises_predict(log, user_features, item_features, model): + with pytest.raises( + ValueError, match="Item features are missing for predict" + ): + model.fit(log, None, item_features) + pred = model.predict_pairs( + log.select("user_idx", "item_idx"), + user_features=None, + item_features=None, + ) + + +def _fit_predict_compare_features( + model, log, user_features, user_features_filtered, item_features, test_ids +): + model.fit( + log, user_features=user_features_filtered, item_features=item_features + ) + + pred_for_test = ( + model.predict_pairs( + test_ids.select("user_idx", "item_idx"), + log, + user_features=user_features, + item_features=item_features, + ) + .select("relevance") + .collect()[0][0] + ) + row_dict = ( + get_first_level_model_features( + model, + test_ids, + user_features=user_features, + item_features=item_features, + ) + .collect()[0] + .asDict() + ) + assert np.isclose( + row_dict["_if_0"] * row_dict["_uf_0"] + + row_dict["_user_bias"] + + row_dict["_item_bias"], + pred_for_test, + ) + + +def test_enrich_with_features(log, user_features, item_features, model): + test_pair = log.filter( + (sf.col("item_idx") == 1) & (sf.col("user_idx") == 1) + ) + + for user_f, item_f in [[None, None], [user_features, item_features]]: + _fit_predict_compare_features( + model, log, user_f, user_f, item_f, test_pair + ) + if item_f is not None: + _fit_predict_compare_features( + model, + log.filter(sf.col("user_idx") != 1), + user_f, + user_f.filter(sf.col("user_idx") != 1), + item_f, + test_pair, + ) diff --git a/tests/models/test_knn.py b/tests/models/test_knn.py index f44c7418c..2447369e7 100644 --- a/tests/models/test_knn.py +++ b/tests/models/test_knn.py @@ -4,6 +4,13 @@ import pytest import numpy as np +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam +from replay.ann.index_builders.driver_nmslib_index_builder import ( + DriverNmslibIndexBuilder, +) +from replay.ann.index_stores.spark_files_index_store import ( + SparkFilesIndexStore, +) from replay.constants import LOG_SCHEMA from replay.models import ItemKNN from tests.utils import spark @@ -23,6 +30,22 @@ def log(spark): ) +@pytest.fixture +def log_2items_per_user(spark): + date = datetime(2019, 1, 1) + return spark.createDataFrame( + data=[ + [0, 0, date, 1.0], + [0, 1, date, 1.0], + [1, 0, date, 1.0], + [1, 1, date, 1.0], + [2, 2, date, 1.0], + [2, 3, date, 1.0], + ], + schema=LOG_SCHEMA, + ) + + @pytest.fixture def weighting_log(spark): date = datetime(2019, 1, 1) @@ -44,6 +67,24 @@ def model(): return model +@pytest.fixture +def model_with_ann(tmp_path): + nmslib_hnsw_params = NmslibHnswParam( + space="negdotprod_sparse", + m=10, + ef_s=200, + ef_c=200, + post=0, + ) + return ItemKNN( + 1, + weighting=None, + index_builder=DriverNmslibIndexBuilder( + index_params=nmslib_hnsw_params, index_store=SparkFilesIndexStore() + ), + ) + + @pytest.fixture def tf_idf_model(): model = ItemKNN(1, weighting="tf_idf") @@ -65,15 +106,9 @@ def test_works(log, model): def test_tf_idf(weighting_log, tf_idf_model): idf = tf_idf_model._get_idf(weighting_log).toPandas() - assert np.allclose( - idf[idf["user_idx"] == 1]["idf"], np.log1p(2 / 1) - ) - assert np.allclose( - idf[idf["user_idx"] == 0]["idf"], np.log1p(2 / 2) - ) - assert np.allclose( - idf[idf["user_idx"] == 2]["idf"], np.log1p(2 / 2) - ) + assert np.allclose(idf[idf["user_idx"] == 1]["idf"], np.log1p(2 / 1)) + assert np.allclose(idf[idf["user_idx"] == 0]["idf"], np.log1p(2 / 2)) + assert np.allclose(idf[idf["user_idx"] == 2]["idf"], np.log1p(2 / 2)) tf_idf_model.fit(weighting_log) recs = tf_idf_model.predict(weighting_log, k=1, users=[0, 1]).toPandas() @@ -118,3 +153,39 @@ def test_weighting_raises(log, tf_idf_model): with pytest.raises(ValueError, match="weighting must be one of .*"): tf_idf_model.weighting = " " log = tf_idf_model._reweight_log(log) + + +def test_knn_predict_filter_seen_items(log, model, model_with_ann): + model.fit(log) + recs1 = model.predict(log, k=1, filter_seen_items=True) + + model_with_ann.fit(log) + recs2 = model_with_ann.predict(log, k=1, filter_seen_items=True) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert recs1.user_idx.equals(recs2.user_idx) + assert recs1.item_idx.equals(recs2.item_idx) + + +def test_knn_predict(log_2items_per_user, model, model_with_ann): + model.fit(log_2items_per_user) + recs1 = model.predict(log_2items_per_user, k=2, filter_seen_items=False) + + model_with_ann.fit(log_2items_per_user) + recs2 = model_with_ann.predict( + log_2items_per_user, k=2, filter_seen_items=False + ) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert all(recs1.user_idx.values == recs2.user_idx.values) + assert all(recs1.item_idx.values == recs2.item_idx.values) diff --git a/tests/models/test_save_load_models.py b/tests/models/test_save_load_models.py index cc06b820a..d4641fbb5 100644 --- a/tests/models/test_save_load_models.py +++ b/tests/models/test_save_load_models.py @@ -9,6 +9,25 @@ from pyspark.sql import functions as sf import replay +from replay.ann.entities.hnswlib_param import HnswlibParam +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam +from replay.ann.index_builders.driver_hnswlib_index_builder import DriverHnswlibIndexBuilder +from replay.ann.index_builders.driver_nmslib_index_builder import ( + DriverNmslibIndexBuilder, +) +from replay.ann.index_builders.executor_hnswlib_index_builder import ( + ExecutorHnswlibIndexBuilder, +) +from replay.ann.index_builders.executor_nmslib_index_builder import ( + ExecutorNmslibIndexBuilder, +) +from replay.ann.index_stores.hdfs_index_store import HdfsIndexStore +from replay.ann.index_stores.shared_disk_index_store import ( + SharedDiskIndexStore, +) +from replay.ann.index_stores.spark_files_index_store import ( + SparkFilesIndexStore, +) from replay.data_preparator import Indexer from replay.model_handler import save, load from replay.models import * @@ -156,3 +175,121 @@ def test_study(df, tmp_path): save(model, path) loaded_model = load(path) assert loaded_model.study == model.study + + +def test_ann_als_saving_loading(long_log_with_features, tmp_path): + model = ALSWrap( + rank=2, + implicit_prefs=False, + seed=42, + index_builder=ExecutorHnswlibIndexBuilder( + index_params=HnswlibParam( + space="ip", + m=100, + ef_c=2000, + post=0, + ef_s=2000, + ), + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), + index_dir="hnswlib_index", + cleanup=False, + ), + ), + ) + + path = (tmp_path / "test").resolve() + model.fit(long_log_with_features) + base_pred = model.predict(long_log_with_features, 5) + save(model, path) + loaded_model = load(path) + new_pred = loaded_model.predict(long_log_with_features, 5) + sparkDataFrameEqual(base_pred, new_pred) + + +def test_ann_word2vec_saving_loading(long_log_with_features, tmp_path): + model = Word2VecRec( + rank=1, window_size=1, use_idf=True, seed=42, min_count=0, + index_builder=DriverHnswlibIndexBuilder( + index_params=HnswlibParam( + space="l2", + m=100, + ef_c=2000, + post=0, + ef_s=2000, + ), + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), + index_dir="hnswlib_index" + ) + ) + ) + + path = (tmp_path / "test").resolve() + model.fit(long_log_with_features) + base_pred = model.predict(long_log_with_features, 5) + save(model, path) + loaded_model = load(path) + new_pred = loaded_model.predict(long_log_with_features, 5) + sparkDataFrameEqual(base_pred, new_pred) + + +def test_ann_slim_saving_loading(long_log_with_features, tmp_path): + nmslib_hnsw_params = NmslibHnswParam( + space="negdotprod_sparse", + m=10, + ef_s=200, + ef_c=200, + post=0, + ) + model = SLIM( + 0.0, + 0.01, + seed=42, + index_builder=DriverNmslibIndexBuilder( + index_params=nmslib_hnsw_params, + index_store=SparkFilesIndexStore(), + ), + ) + + path = (tmp_path / "test").resolve() + model.fit(long_log_with_features) + base_pred = model.predict(long_log_with_features, 5) + save(model, path) + loaded_model = load(path) + new_pred = loaded_model.predict(long_log_with_features, 5) + sparkDataFrameEqual(base_pred, new_pred) + + +def test_ann_knn_saving_loading(long_log_with_features, tmp_path): + nmslib_hnsw_params = NmslibHnswParam( + space="negdotprod_sparse", + m=10, + ef_s=200, + ef_c=200, + post=0, + ) + model = ItemKNN( + 1, + weighting=None, + index_builder=ExecutorNmslibIndexBuilder( + index_params=nmslib_hnsw_params, + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), index_dir="nmslib_hnsw_index" + ), + ), + ) + + path = (tmp_path / "test").resolve() + model.fit(long_log_with_features) + base_pred = model.predict(long_log_with_features, 5) + save(model, path) + loaded_model = load(path) + new_pred = loaded_model.predict(long_log_with_features, 5) + sparkDataFrameEqual(base_pred, new_pred) + + +def test_hdfs_index_store_exception(): + local_warehouse_dir = 'file:///tmp' + with pytest.raises(ValueError, match=f"Can't recognize path {local_warehouse_dir + '/index_dir'} as HDFS path!"): + HdfsIndexStore(warehouse_dir=local_warehouse_dir, index_dir="index_dir") diff --git a/tests/models/test_slim.py b/tests/models/test_slim.py index 384ca64c9..a6aa014c6 100644 --- a/tests/models/test_slim.py +++ b/tests/models/test_slim.py @@ -3,6 +3,13 @@ import pytest import numpy as np +from replay.ann.entities.nmslib_hnsw_param import NmslibHnswParam +from replay.ann.index_builders.executor_nmslib_index_builder import ( + ExecutorNmslibIndexBuilder, +) +from replay.ann.index_stores.shared_disk_index_store import ( + SharedDiskIndexStore, +) from replay.models import SLIM from tests.utils import log, spark @@ -12,6 +19,28 @@ def model(): return SLIM(0.0, 0.01, seed=42) +@pytest.fixture +def model_with_ann(tmp_path): + nmslib_hnsw_params = NmslibHnswParam( + space="negdotprod_sparse", + m=10, + ef_s=200, + ef_c=200, + post=0, + ) + return SLIM( + 0.0, + 0.01, + seed=42, + index_builder=ExecutorNmslibIndexBuilder( + index_params=nmslib_hnsw_params, + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), index_dir="nmslib_hnsw_index" + ), + ), + ) + + def test_fit(log, model): model.fit(log) assert np.allclose( @@ -42,6 +71,23 @@ def test_predict(log, model): ) +def test_ann_predict(log, model, model_with_ann): + model.fit(log) + recs1 = model.predict(log, k=1) + + model_with_ann.fit(log) + recs2 = model_with_ann.predict(log, k=1) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert recs1.user_idx.equals(recs2.user_idx) + assert recs1.item_idx.equals(recs2.item_idx) + + @pytest.mark.parametrize( "beta,lambda_", [(0.0, 0.0), (-0.1, 0.1), (0.1, -0.1)] ) diff --git a/tests/models/test_word2vec.py b/tests/models/test_word2vec.py index 1a8c08e1a..a0693475c 100644 --- a/tests/models/test_word2vec.py +++ b/tests/models/test_word2vec.py @@ -5,10 +5,13 @@ import numpy as np from pyspark.sql import functions as sf +from replay.ann.entities.hnswlib_param import HnswlibParam +from replay.ann.index_builders.driver_hnswlib_index_builder import DriverHnswlibIndexBuilder +from replay.ann.index_stores.shared_disk_index_store import SharedDiskIndexStore from replay.constants import LOG_SCHEMA from replay.models import Word2VecRec from replay.utils import vector_dot -from tests.utils import spark +from tests.utils import spark, log as log2 @pytest.fixture @@ -35,6 +38,27 @@ def model(): ) +@pytest.fixture +def model_with_ann(tmp_path): + model = Word2VecRec( + rank=1, window_size=1, use_idf=True, seed=42, min_count=0, + index_builder=DriverHnswlibIndexBuilder( + index_params=HnswlibParam( + space="ip", + m=100, + ef_c=2000, + post=0, + ef_s=2000, + ), + index_store=SharedDiskIndexStore( + warehouse_dir=str(tmp_path), + index_dir="hnswlib_index" + ) + ) + ) + return model + + def test_fit(log, model): model.fit(log) vectors = ( @@ -60,3 +84,38 @@ def test_predict(log, model): recs.toPandas().sort_values("user_idx").relevance, [1.0003180271011836, 0.9653348251181987, 0.972993367280087], ) + + +# here we use `test.utils.log` because we can't build the hnsw index on `log` data +def test_word2vec_predict_filter_seen_items(log2, model, model_with_ann): + model.fit(log2) + recs1 = model.predict(log2, k=1) + + model_with_ann.fit(log2) + recs2 = model_with_ann.predict(log2, k=1) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert recs1.user_idx.equals(recs2.user_idx) + assert recs1.item_idx.equals(recs2.item_idx) + + +def test_word2vec_predict(log2, model, model_with_ann): + model.fit(log2) + recs1 = model.predict(log2, k=2, filter_seen_items=False) + + model_with_ann.fit(log2) + recs2 = model_with_ann.predict(log2, k=2, filter_seen_items=False) + + recs1 = recs1.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + recs2 = recs2.toPandas().sort_values( + ["user_idx", "item_idx"], ascending=False + ) + assert recs1.user_idx.equals(recs2.user_idx) + assert recs1.item_idx.equals(recs2.item_idx)