diff --git a/.gitignore b/.gitignore index 32780e5..b85c71d 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ ENV/ # Rope project settings .ropeproject + +# VS Code +.vscode diff --git a/poetry.lock b/poetry.lock index 5b50aed..a91c7c0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,72 +1,242 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + [[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" +name = "black" +version = "23.11.0" +description = "The uncompromising code formatter." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [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]"] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "exceptiongroup" -version = "1.1.0" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] [package.extras] test = ["pytest (>=6)"] [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.1" description = "Lightweight in-process concurrent programming" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, + {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, + {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, + {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, + {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, + {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, + {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, + {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, + {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, + {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, + {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, + {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, + {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, + {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, + {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, +] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx"] test = ["objgraph", "psutil"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] [[package]] name = "packaging" -version = "23.0" +version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] [package.extras] dev = ["pre-commit", "tox"] @@ -76,17 +246,23 @@ testing = ["pytest", "pytest-benchmark"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] [[package]] name = "pyrql" version = "0.6.2" description = "RQL parsing" -category = "main" optional = false python-versions = ">=3.5,<4.0" +files = [ + {file = "pyrql-0.6.2-py3-none-any.whl", hash = "sha256:97ba5ae0424618b7b929678a1f3838f3c6d6f7a1fc3fc3f2efc3a87e94e7f961"}, + {file = "pyrql-0.6.2.tar.gz", hash = "sha256:f606d73c9a2507715be3e97c2cd4ab43a52f65481a0cedb2c2392624fa8c0395"}, +] [package.dependencies] pyparsing = ">=2.4,<3.0" @@ -94,14 +270,16 @@ python-dateutil = ">=2.8,<3.0" [[package]] name = "pytest" -version = "7.2.1" +version = "7.4.3" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" @@ -110,15 +288,18 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" @@ -127,206 +308,123 @@ six = ">=1.5" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "sqlalchemy" -version = "1.4.46" +version = "2.0.23" description = "Database Abstraction Library" -category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-win32.whl", hash = "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-win32.whl", hash = "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"}, + {file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"}, + {file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"}, +] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.2.0" [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] +oracle = ["cx-oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3_binary"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "d0c0fd4de7b9d0eaa69c3bfe8e44bafece8ee60261a502a0c866fc46c22d1125" - -[metadata.files] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] -greenlet = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -packaging = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pyrql = [ - {file = "pyrql-0.6.2-py3-none-any.whl", hash = "sha256:97ba5ae0424618b7b929678a1f3838f3c6d6f7a1fc3fc3f2efc3a87e94e7f961"}, - {file = "pyrql-0.6.2.tar.gz", hash = "sha256:f606d73c9a2507715be3e97c2cd4ab43a52f65481a0cedb2c2392624fa8c0395"}, -] -pytest = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -sqlalchemy = [ - {file = "SQLAlchemy-1.4.46-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7001f16a9a8e06488c3c7154827c48455d1c1507d7228d43e781afbc8ceccf6d"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c7a46639ba058d320c9f53a81db38119a74b8a7a1884df44d09fbe807d028aaf"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win32.whl", hash = "sha256:c04144a24103135ea0315d459431ac196fe96f55d3213bfd6d39d0247775c854"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win_amd64.whl", hash = "sha256:7b81b1030c42b003fc10ddd17825571603117f848814a344d305262d370e7c34"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:939f9a018d2ad04036746e15d119c0428b1e557470361aa798e6e7d7f5875be0"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b7f4b6aa6e87991ec7ce0e769689a977776db6704947e562102431474799a857"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf17ac9a61e7a3f1c7ca47237aac93cabd7f08ad92ac5b96d6f8dea4287fc1"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f8267682eb41a0584cf66d8a697fef64b53281d01c93a503e1344197f2e01fe"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cb0ad8a190bc22d2112001cfecdec45baffdf41871de777239da6a28ed74b6"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win32.whl", hash = "sha256:5f752676fc126edc1c4af0ec2e4d2adca48ddfae5de46bb40adbd3f903eb2120"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win_amd64.whl", hash = "sha256:31de1e2c45e67a5ec1ecca6ec26aefc299dd5151e355eb5199cd9516b57340be"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d68e1762997bfebf9e5cf2a9fd0bcf9ca2fdd8136ce7b24bbd3bbfa4328f3e4a"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d112b0f3c1bc5ff70554a97344625ef621c1bfe02a73c5d97cac91f8cd7a41e"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fac0a7054d86b997af12dc23f581cf0b25fb1c7d1fed43257dee3af32d3d6d"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win32.whl", hash = "sha256:887865924c3d6e9a473dc82b70977395301533b3030d0f020c38fd9eba5419f2"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win_amd64.whl", hash = "sha256:984ee13543a346324319a1fb72b698e521506f6f22dc37d7752a329e9cd00a32"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9167d4227b56591a4cc5524f1b79ccd7ea994f36e4c648ab42ca995d28ebbb96"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d61e9ecc849d8d44d7f80894ecff4abe347136e9d926560b818f6243409f3c86"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3ec187acf85984263299a3f15c34a6c0671f83565d86d10f43ace49881a82718"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9883f5fae4fd8e3f875adc2add69f8b945625811689a6c65866a35ee9c0aea23"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win32.whl", hash = "sha256:535377e9b10aff5a045e3d9ada8a62d02058b422c0504ebdcf07930599890eb0"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win_amd64.whl", hash = "sha256:18cafdb27834fa03569d29f571df7115812a0e59fd6a3a03ccb0d33678ec8420"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:a1ad90c97029cc3ab4ffd57443a20fac21d2ec3c89532b084b073b3feb5abff3"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4847f4b1d822754e35707db913396a29d874ee77b9c3c3ef3f04d5a9a6209618"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c5a99282848b6cae0056b85da17392a26b2d39178394fc25700bcf967e06e97a"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b1cc7835b39835c75cf7c20c926b42e97d074147c902a9ebb7cf2c840dc4e2"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win32.whl", hash = "sha256:c522e496f9b9b70296a7675272ec21937ccfc15da664b74b9f58d98a641ce1b6"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win_amd64.whl", hash = "sha256:ae067ab639fa499f67ded52f5bc8e084f045d10b5ac7bb928ae4ca2b6c0429a5"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e3c1808008124850115a3f7e793a975cfa5c8a26ceeeb9ff9cbb4485cac556df"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d164df3d83d204c69f840da30b292ac7dc54285096c6171245b8d7807185aa"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b33ffbdbbf5446cf36cd4cc530c9d9905d3c2fe56ed09e25c22c850cdb9fac92"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d94682732d1a0def5672471ba42a29ff5e21bb0aae0afa00bb10796fc1e28dd"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win32.whl", hash = "sha256:f8cb80fe8d14307e4124f6fad64dfd87ab749c9d275f82b8b4ec84c84ecebdbe"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win_amd64.whl", hash = "sha256:07e48cbcdda6b8bc7a59d6728bd3f5f574ffe03f2c9fb384239f3789c2d95c2e"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1b1e5e96e2789d89f023d080bee432e2fef64d95857969e70d3cadec80bd26f0"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3714e5b33226131ac0da60d18995a102a17dddd42368b7bdd206737297823ad"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:955162ad1a931fe416eded6bb144ba891ccbf9b2e49dc7ded39274dd9c5affc5"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6e4cb5c63f705c9d546a054c60d326cbde7421421e2d2565ce3e2eee4e1a01f"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win32.whl", hash = "sha256:51e1ba2884c6a2b8e19109dc08c71c49530006c1084156ecadfaadf5f9b8b053"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win_amd64.whl", hash = "sha256:315676344e3558f1f80d02535f410e80ea4e8fddba31ec78fe390eff5fb8f466"}, - {file = "SQLAlchemy-1.4.46.tar.gz", hash = "sha256:6913b8247d8a292ef8315162a51931e2b40ce91681f1b6f18f697045200c4a30"}, -] -tomli = [ +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] + +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "c045043dee03e09d7591b9c0f7d39aa6823aad7cb4ffe636a64ccaaf60b1ccfe" diff --git a/pyproject.toml b/pyproject.toml index e34caab..c6c98d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,12 @@ keywords = ['sqlachemy', 'sql', 'rql', 'querying', 'httpapi'] [tool.poetry.dependencies] python = "^3.8" pyrql = "^0.6.0" -sqlalchemy = "<2.0" +sqlalchemy = "^2.0" -[tool.poetry.dev-dependencies] -pytest = ">=4.4.1" +[tool.poetry.group.dev.dependencies] +isort = "^5.12.0" +black = "^23.11.0" +pytest = "^7.4.3" [build-system] requires = ["poetry>=0.12"] diff --git a/rqlalchemy/__init__.py b/rqlalchemy/__init__.py index ca8a40f..4d9b63f 100644 --- a/rqlalchemy/__init__.py +++ b/rqlalchemy/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from rqlalchemy.query import RQLQueryError -from rqlalchemy.query import RQLQueryMixIn +from rqlalchemy.query import RQLSelect +from rqlalchemy.query import RQLSelectError __title__ = "rqlalchemy" __version__ = "0.4.5" @@ -10,4 +10,4 @@ __license__ = "MIT" -__all__ = ["RQLQueryMixIn", "RQLQueryError"] +__all__ = ["RQLSelect", "RQLSelectError"] diff --git a/rqlalchemy/query.py b/rqlalchemy/query.py index e1ecc27..25842f0 100644 --- a/rqlalchemy/query.py +++ b/rqlalchemy/query.py @@ -4,119 +4,205 @@ import operator from copy import deepcopy from functools import reduce +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Union -import sqlalchemy from pyrql import RQLSyntaxError from pyrql import parse from pyrql import unparse -from sqlalchemy import and_ +from sqlalchemy import ColumnElement +from sqlalchemy import Row +from sqlalchemy import RowMapping +from sqlalchemy import Select from sqlalchemy import func -from sqlalchemy import not_ -from sqlalchemy import or_ +from sqlalchemy import sql from sqlalchemy.inspection import inspect +from sqlalchemy.orm import Session +from sqlalchemy.orm import decl_api from sqlalchemy.orm.exc import MultipleResultsFound from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.sql import _typing +from sqlalchemy.sql import elements -SQLALCHEMY_VERSION = tuple(int(v) for v in sqlalchemy.__version__.split(".")) +ArgsType = List[Any] +BinaryOperator = Callable[[Any, Any], Any] -class RQLQueryError(Exception): - pass +class PaginatedResults(NamedTuple): + page: Any + total: int + previous_page: Optional[str] = None + next_page: Optional[str] = None + +class RQLSelectError(Exception): + pass -class RQLQueryMixIn: - """Query mix-in class with RQL functions""" - _rql_error_cls = RQLQueryError +class RQLSelect(Select): + inherit_cache = True + _rql_error_cls = RQLSelectError _rql_max_limit = None _rql_default_limit = None _rql_auto_scalar = False + def __init__(self, *entities: _typing._ColumnsClauseArgument[Any]): + super().__init__(*entities) + self._rql_select_clause = [] + self._rql_values_clause = None + self._rql_scalar_clause = None + self._rql_where_clause = None + self._rql_order_by_clause = None + self._rql_limit_clause = None + self._rql_offset_clause = None + self._rql_one_clause = None + self._rql_distinct_clause = None + self._rql_group_by_clause = None + self._rql_joins = [] + self._rql_aliased_models = {} + # properties added for compatibility with sqlalchemy < 1.4 @property - def _rql_compat_entities(self): - if SQLALCHEMY_VERSION < (1, 4, 0): - return [e.type for e in self._entities] - else: - return [t._annotations["parententity"].entity for t in self._raw_columns] + def _rql_select_entities(self) -> List[decl_api.DeclarativeMeta]: + return [t._annotations["parententity"].entity for t in self._raw_columns] @property - def _rql_compat_limit(self): - if SQLALCHEMY_VERSION < (1, 4, 0): - return self._limit - else: - return self._limit_clause.value if self._limit_clause is not None else None + def _rql_select_limit(self): + return self._limit_clause.value if self._limit_clause is not None else None @property - def _rql_compat_offset(self): - if SQLALCHEMY_VERSION < (1, 4, 0): - return self._offset - else: - return self._offset_clause.value if self._offset_clause is not None else None + def _rql_select_offset(self): + return self._offset_clause.value if self._offset_clause is not None else None - def rql(self, query, limit=None): - if len(self._rql_compat_entities) > 1: - raise NotImplementedError("Query must have a single entity") + def rql(self, query: str = "", limit: Optional[int] = None) -> "RQLSelect": + if len(self._rql_select_entities) > 1: + raise NotImplementedError("Select must have only one entity") - expr = query - - if not expr: + if not query: self.rql_parsed = None - self.rql_expr = "" - else: - self.rql_expr = expr + self.rql_expression = query try: - self.rql_parsed = parse(expr) - except RQLSyntaxError as exc: - raise self._rql_error_cls("RQL Syntax error: %r" % (exc.args,)) - - self._rql_select_clause = [] - self._rql_values_clause = None - self._rql_scalar_clause = None - self._rql_where_clause = None - self._rql_order_by_clause = None - self._rql_limit_clause = None - self._rql_offset_clause = None - self._rql_one_clause = None - self._rql_distinct_clause = None - self._rql_group_by_clause = None - self._rql_joins = [] + self.rql_parsed: Dict[str, Any] = parse(query) + except RQLSyntaxError as e: + raise self._rql_error_cls(f"RQL Syntax error: {e.args}") self._rql_walk(self.rql_parsed) - query = self + select_ = self for other in self._rql_joins: - query = query.join(other) + select_ = select_.outerjoin(other) if self._rql_where_clause is not None: - query = query.filter(self._rql_where_clause) + select_ = select_.filter(self._rql_where_clause) if self._rql_order_by_clause is not None: - query = query.order_by(*self._rql_order_by_clause) + select_ = select_.order_by(*self._rql_order_by_clause) - # limit priority is: default, method parameter, querystring parameter - if self._rql_default_limit: - query = query.limit(self._rql_default_limit) + if self._rql_default_limit is not None: + select_ = select_.limit(self._rql_default_limit) if limit is not None: - query = query.limit(limit) + select_ = select_.limit(limit) if self._rql_limit_clause is not None: - query = query.limit(self._rql_limit_clause) + select_ = select_.limit(self._rql_limit_clause) if self._rql_offset_clause is not None: - query = query.offset(self._rql_offset_clause) + select_ = select_.offset(self._rql_offset_clause) if self._rql_distinct_clause is not None: - query = query.distinct() + select_ = select_.distinct() + + return select_ + + def rql_all(self, session: Session) -> Sequence[Union[Union[Row, RowMapping], Any]]: + """ + Executes the sql expression differently based on which clauses included: + - For single aggregates a scalar is returned + - In case the one clause is included only a single row is returned + - In case a select clause is included only the requisite fields are returned + - Otherwise scalars are returned + """ + if self._rql_scalar_clause is not None: + if self._rql_scalar_clause.__class__.__name__ == "count": + return session.scalar(select(self._rql_scalar_clause).select_from(self.subquery())) + return session.scalar(self.with_only_columns(self._rql_scalar_clause)) + + if self._rql_one_clause is not None: + try: + return [session.scalars(self).one()] + except NoResultFound: + raise RQLSelectError("No result found for one()") + except MultipleResultsFound: + raise RQLSelectError("Multiple results found for one()") + + if self._rql_values_clause is not None: + query = self.with_only_columns(self._rql_values_clause) + if self._rql_distinct_clause is not None: + query = query.distinct() + + return [row[0] for row in session.execute(query)] + + if self._rql_select_clause: + query = self.with_only_columns(*self._rql_select_clause) + + if self._rql_group_by_clause: + query = query.group_by(*self._rql_group_by_clause) + + if self._rql_distinct_clause is not None: + query = query.distinct() + + return [row._asdict() for row in session.execute(query)] + + return session.scalars(self).all() + + def rql_paginate(self, session: Session) -> PaginatedResults: + """ + Convenience function for pagination. Returns: + - the page given to the rql query + - the count by setting the limit, offset and order by to None + - next and last page rql queries if more records are available for pagination + """ + + limit = self._rql_select_limit + offset = self._rql_select_offset or 0 + + if limit is None: + raise RQLSelectError("Pagination requires a limit value") + + page = self.rql_all(session) + + total_query = self.limit(None).offset(None).order_by(None) + total_query_count = sql.select(func.count()).select_from(total_query.subquery()) + total = session.scalar(total_query_count) + + if offset + limit < total: + expr = self.rql_expr_replace({"name": "limit", "args": [limit, offset + limit]}) + next_page = expr + else: + next_page = None - return query + if offset > 0 and total: + expr = self.rql_expr_replace({"name": "limit", "args": [limit, offset - limit]}) + previous_page = expr + else: + previous_page = None - def rql_expr_replace(self, replacement): + return PaginatedResults( + page=page, total=total, previous_page=previous_page, next_page=next_page + ) + + def rql_expr_replace(self, replacement: Dict[str, Any]) -> str: """Replace any nodes matching the replacement name This can be used to generate an expression with modified @@ -132,7 +218,7 @@ def rql_expr_replace(self, replacement): return unparse(parsed) - def _rql_traverse_and_replace(self, root, name, args): + def _rql_traverse_and_replace(self, root: Dict[str, Any], name: str, args: ArgsType) -> bool: if root is None: return False @@ -148,19 +234,17 @@ def _rql_traverse_and_replace(self, root, name, args): return False - def _rql_walk(self, node): - # filtering nodes will be used by the where clause. Other - # nodes will be saved separately by the visitor methods below + def _rql_walk(self, node: Dict[str, Any]) -> None: if node: self._rql_where_clause = self._rql_apply(node) - def _rql_apply(self, node): + def _rql_apply(self, node: Dict[str, Any]) -> Any: if isinstance(node, dict): name = node["name"] args = node["args"] if name in {"eq", "ne", "lt", "le", "gt", "ge"}: - return self._rql_cmp(args, getattr(operator, name)) + return self._rql_compare(args, getattr(operator, name)) try: method = getattr(self, "_rql_" + name) @@ -178,7 +262,7 @@ def _rql_apply(self, node): return node def _rql_attr(self, attr): - model = self._rql_compat_entities[0] + model = self._rql_select_entities[0] if isinstance(attr, str): try: @@ -190,9 +274,9 @@ def _rql_attr(self, attr): # Every entry in attr but the last should be a relationship name. for name in attr[:-1]: if name in inspect(model).relationships: - rel = getattr(model, name) - self._rql_joins.append(rel) - model = rel.mapper.class_ + relation = getattr(model, name) + self._rql_joins.append(relation) + model = relation.mapper.class_ else: raise AttributeError(f'{model} has no relationship "{name}"') # Get the column from the last entry in attr. @@ -201,58 +285,56 @@ def _rql_attr(self, attr): raise NotImplementedError - def _rql_value(self, value, attr=None): + def _rql_value(self, value: Any) -> Any: if isinstance(value, dict): value = self._rql_apply(value) return value - def _rql_cmp(self, args, op): + def _rql_compare(self, args: ArgsType, op: BinaryOperator) -> elements.BinaryExpression: attr, value = args - - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) + attr = self._rql_attr(attr=attr) + value = self._rql_value(value) return op(attr, value) - def _rql_and(self, args): + def _rql_and(self, args: ArgsType) -> Optional[elements.BooleanClauseList]: args = [self._rql_apply(node) for node in args] args = [a for a in args if a is not None] + if args: - return reduce(and_, args) + return reduce(sql.and_, args) - def _rql_or(self, args): + def _rql_or(self, args: ArgsType) -> Optional[elements.BooleanClauseList]: args = [self._rql_apply(node) for node in args] args = [a for a in args if a is not None] + if args: - return reduce(or_, args) + return reduce(sql.or_, args) - def _rql_in(self, args): + def _rql_in(self, args: ArgsType) -> elements.BinaryExpression: attr, value = args - - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) + attr = self._rql_attr(attr=attr) + value = self._rql_value([str(v) for v in value]) return attr.in_(value) - def _rql_out(self, args): + def _rql_out(self, args: ArgsType) -> elements.BinaryExpression: attr, value = args + attr = self._rql_attr(attr=attr) + value = self._rql_value([str(v) for v in value]) - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) - - return not_(attr.in_(value)) + return sql.not_(attr.in_(value)) - def _rql_like(self, args): + def _rql_like(self, args: ArgsType) -> elements.BinaryExpression: attr, value = args - - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) + attr = self._rql_attr(attr=attr) + value = self._rql_value(value) value = value.replace("*", "%") return attr.like(value) - def _rql_limit(self, args): + def _rql_limit(self, args: ArgsType) -> None: args = [self._rql_value(v) for v in args] self._rql_limit_clause = min(args[0], self._rql_max_limit or float("inf")) @@ -260,156 +342,102 @@ def _rql_limit(self, args): if len(args) == 2: self._rql_offset_clause = args[1] - def _rql_sort(self, args): - # normalize sort args with '+' + def _rql_sort(self, args: ArgsType) -> None: args = [("+", v) if isinstance(v, str) else v for v in args] - # pair signals with attributes - args = [(p, self._rql_attr(v)) for (p, v) in args] - + args = [(p, self._rql_attr(attr=v)) for (p, v) in args] attrs = [attr.desc() if p == "-" else attr for (p, attr) in args] self._rql_order_by_clause = attrs - def _rql_contains(self, args): + def _rql_contains(self, args: ArgsType) -> ColumnElement[bool]: attr, value = args - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) + attr = self._rql_attr(attr=attr) + value = self._rql_value(value) return attr.contains(value) - def _rql_excludes(self, args): + def _rql_excludes(self, args: ArgsType) -> ColumnElement[bool]: attr, value = args - attr = self._rql_attr(attr) - value = self._rql_value(value, attr) + attr = self._rql_attr(attr=attr) + value = self._rql_value(value) - return not_(attr.contains(value)) + return sql.not_(attr.contains(value)) - def _rql_select(self, args): + def _rql_select(self, args: ArgsType) -> None: attrs = [self._rql_attr(attr) for attr in args] + self._rql_select_clause = attrs - def _rql_values(self, args): + def _rql_values(self, args: ArgsType) -> None: (attr,) = args attr = self._rql_attr(attr) + self._rql_values_clause = attr - def _rql_distinct(self, args): + def _rql_distinct(self, *_) -> None: self._rql_distinct_clause = True - def _rql_sum(self, args): + def _rql_sum(self, args: ArgsType) -> None: (attr,) = args - attr = self._rql_attr(attr) - self._rql_values_clause = func.sum(attr) + attr = self._rql_attr(attr=attr) + self._rql_scalar_clause = func.sum(attr) - def _rql_mean(self, args): + def _rql_mean(self, args: ArgsType) -> None: (attr,) = args - attr = self._rql_attr(attr) + attr = self._rql_attr(attr=attr) + self._rql_scalar_clause = func.avg(attr) - def _rql_max(self, args): + def _rql_max(self, args: ArgsType) -> None: (attr,) = args - attr = self._rql_attr(attr) + attr = self._rql_attr(attr=attr) + self._rql_scalar_clause = func.max(attr) - def _rql_min(self, args): + def _rql_min(self, args: ArgsType) -> None: (attr,) = args - attr = self._rql_attr(attr) + attr = self._rql_attr(attr=attr) + self._rql_scalar_clause = func.min(attr) - def _rql_count(self, args): + def _rql_count(self, *_) -> None: self._rql_scalar_clause = func.count() - def _rql_first(self, args): + def _rql_first(self, *_) -> None: self._rql_limit_clause = 1 - def _rql_one(self, args): + def _rql_one(self, *_) -> None: self._rql_one_clause = True - def _rql_time(self, args): + def _rql_time(self, args: ArgsType) -> datetime.time: return datetime.time(*args) - def _rql_date(self, args): + def _rql_date(self, args: ArgsType) -> datetime.date: return datetime.date(*args) - def _rql_dt(self, args): + def _rql_dt(self, args: ArgsType) -> datetime.datetime: return datetime.datetime(*args) - def _rql_aggregate(self, args): - attrs = [] - aggrs = [] + def _rql_aggregate(self, args: ArgsType) -> None: + attributes = [] + aggregations = [] - for x in args: - if isinstance(x, dict): - agg_label = x["name"] - agg_func = getattr(func, x["name"]) - agg_attr = self._rql_attr(x["args"][0]) + for argument in args: + if isinstance(argument, dict): + aggregate_label = argument["name"] + aggregate_function = getattr(func, argument["name"]) + aggregate_attribute = self._rql_attr(argument["args"][0]) - aggrs.append(agg_func(agg_attr).label(agg_label)) + aggregations.append(aggregate_function(aggregate_attribute).label(aggregate_label)) else: - attrs.append(self._rql_attr(x)) - - self._rql_group_by_clause = attrs - self._rql_select_clause = attrs + aggrs + attributes.append(self._rql_attr(argument)) - def rql_all(self): + self._rql_group_by_clause = attributes + self._rql_select_clause = attributes + aggregations - if self._rql_scalar_clause is not None: - return self.from_self(self._rql_scalar_clause).scalar() - - if self._rql_one_clause is not None: - try: - return [self.one()] - except NoResultFound: - raise RQLQueryError("No result found for one()") - except MultipleResultsFound: - raise RQLQueryError("Multiple results found for one()") - - if self._rql_values_clause is not None: - query = self.from_self(self._rql_values_clause) - if self._rql_distinct_clause is not None: - query = query.distinct() - - return [row[0] for row in query] - - if self._rql_select_clause: - query = self.from_self(*self._rql_select_clause) - - if self._rql_group_by_clause: - query = query.group_by(*self._rql_group_by_clause) - - if self._rql_distinct_clause is not None: - query = query.distinct() - - return [row._asdict() for row in query] - - return self.all() - - def rql_paginate(self): - limit = self._rql_compat_limit - offset = self._rql_compat_offset or 0 - total = 0 - - if limit is None: - raise RQLQueryError("Pagination requires a limit value") - - # build a bare query copy to calculate totals - _total_query = self.limit(None).offset(None).order_by(None) - # then replace the select clause with count(*) and get the first value - total = _total_query.count() - - page = self.rql_all() - - if offset + limit < total: - expr = self.rql_expr_replace({"name": "limit", "args": [limit, offset + limit]}) - next_page = expr - else: - next_page = None - - if offset > 0 and total: - expr = self.rql_expr_replace({"name": "limit", "args": [limit, offset - limit]}) - previous_page = expr - else: - previous_page = None - return page, previous_page, next_page, total +def select(*entities: _typing._ColumnsClauseArgument[Any], **__kw: Any) -> RQLSelect: + if __kw: + raise _typing._no_kw() + return RQLSelect(*entities) diff --git a/tests/conftest.py b/tests/conftest.py index b7b046f..f7d1cbb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,14 +7,11 @@ from fixtures import Base from fixtures import Blog from fixtures import Post -from fixtures import RQLQuery from fixtures import Tag from fixtures import User from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from rqlalchemy.query import SQLALCHEMY_VERSION - @pytest.fixture(scope="session") def engine(): @@ -26,7 +23,7 @@ def session(engine): Base.metadata.create_all(engine) - session_ = sessionmaker(bind=engine, query_cls=RQLQuery) + session_ = sessionmaker(bind=engine) # load fixtures fpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "users.json") @@ -65,10 +62,7 @@ def session(engine): def blogs(session): blogs = [] for uid in range(3): - if SQLALCHEMY_VERSION < (1, 4, 0): - user = session.query(User).get(uid) - else: - user = session.get(User, uid) + user = session.get(User, uid) for blog_no in range(3): blog = Blog(title=f"Blog {blog_no} for {user.name}", user=user) blogs.append(blog) diff --git a/tests/fixtures.py b/tests/fixtures.py index b4063bb..4e2378e 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -9,19 +9,12 @@ from dateutil.parser import parse as parse_dt from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import Query as BaseQuery from sqlalchemy.orm import relationship from sqlalchemy.orm import validates -from rqlalchemy import RQLQueryMixIn - Base = declarative_base() -class RQLQuery(BaseQuery, RQLQueryMixIn): - pass - - class Tag(Base): __tablename__ = "tag" diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 9ff4595..a1d3521 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,98 +1,92 @@ import pytest from fixtures import User -from rqlalchemy import RQLQueryError +from rqlalchemy import RQLSelectError +from rqlalchemy.query import select class TestPagination: def test_pagination_no_limit_raises_error(self, session): - with pytest.raises(RQLQueryError): - session.query(User).rql("").rql_paginate() + with pytest.raises(RQLSelectError): + select(User).rql("").rql_paginate(session) def test_pagination_no_filter(self, session): - res = session.query(User).rql("limit(10)").rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql("limit(10)").rql_paginate(session) - assert previous_page is None - assert next_page == "limit(10,10)" - assert total == 1000 + assert res.previous_page is None + assert res.next_page == "limit(10,10)" + assert res.total == 1000 - exp = session.query(User).limit(10).all() - assert page - assert page == exp + exp = session.scalars(select(User).limit(10)).all() + assert res.page + assert res.page == exp def test_pagination_with_filter(self, session): - res = session.query(User).rql("and(in(state,(FL,TX)),limit(10))").rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql("and(in(state,(FL,TX)),limit(10))").rql_paginate(session) - assert previous_page is None - assert next_page == "and(in(state,(FL,TX)),limit(10,10))" - assert total == 34 + assert res.previous_page is None + assert res.next_page == "and(in(state,(FL,TX)),limit(10,10))" + assert res.total == 34 - exp = session.query(User).filter(User.state.in_(("FL", "TX"))).limit(10).all() - assert page - assert page == exp + exp = session.scalars(select(User).filter(User.state.in_(("FL", "TX"))).limit(10)).all() + assert res.page + assert res.page == exp def test_pagination_with_filter_and_order(self, session): - res = session.query(User).rql("and(in(state,(FL,TX)),limit(10),sort(name))").rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql("and(in(state,(FL,TX)),limit(10),sort(name))").rql_paginate(session) - assert previous_page is None - assert next_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" - assert total == 34 + assert res.previous_page is None + assert res.next_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" + assert res.total == 34 - exp = session.query(User).filter(User.state.in_(("FL", "TX"))).order_by(User.name).limit(10).all() - assert page - assert page == exp + exp = session.scalars(select(User).filter(User.state.in_(("FL", "TX"))).order_by(User.name).limit(10)).all() + assert res.page + assert res.page == exp def test_pagination_with_filter_and_order_all_pages(self, session): next_page = "and(in(state,(FL,TX)),limit(10),sort(name))" - query = session.query(User).filter(User.state.in_(("FL", "TX"))).order_by(User.name) + query = select(User).filter(User.state.in_(("FL", "TX"))).order_by(User.name) # page 1 - res = session.query(User).rql(next_page).rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql(next_page).rql_paginate(session) - assert previous_page is None - assert next_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" - assert total == 34 + assert res.previous_page is None + assert res.next_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" + assert res.total == 34 - exp = query.limit(10).all() - assert len(page) == 10 - assert page == exp + exp = session.scalars(query.limit(10)).all() + assert len(res.page) == 10 + assert res.page == exp # page 2 - res = session.query(User).rql(next_page).rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql(res.next_page).rql_paginate(session) - assert previous_page == "and(in(state,(FL,TX)),limit(10,0),sort(name))" - assert next_page == "and(in(state,(FL,TX)),limit(10,20),sort(name))" - assert total == 34 + assert res.previous_page == "and(in(state,(FL,TX)),limit(10,0),sort(name))" + assert res.next_page == "and(in(state,(FL,TX)),limit(10,20),sort(name))" + assert res.total == 34 - exp = query.limit(10).offset(10).all() - assert len(page) == 10 - assert page == exp + exp = session.scalars(query.limit(10).offset(10)).all() + assert len(res.page) == 10 + assert res.page == exp # page 3 - res = session.query(User).rql(next_page).rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql(res.next_page).rql_paginate(session) - assert previous_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" - assert next_page == "and(in(state,(FL,TX)),limit(10,30),sort(name))" - assert total == 34 + assert res.previous_page == "and(in(state,(FL,TX)),limit(10,10),sort(name))" + assert res.next_page == "and(in(state,(FL,TX)),limit(10,30),sort(name))" + assert res.total == 34 - exp = query.limit(10).offset(20).all() - assert len(page) == 10 - assert page == exp + exp = session.scalars(query.limit(10).offset(20)).all() + assert len(res.page) == 10 + assert res.page == exp # page 4 - res = session.query(User).rql(next_page).rql_paginate() - page, previous_page, next_page, total = res + res = select(User).rql(res.next_page).rql_paginate(session) - assert previous_page == "and(in(state,(FL,TX)),limit(10,20),sort(name))" - assert next_page is None - assert total == 34 + assert res.previous_page == "and(in(state,(FL,TX)),limit(10,20),sort(name))" + assert res.next_page is None + assert res.total == 34 - exp = query.limit(10).offset(30).all() - assert len(page) == 4 - assert page == exp + exp = session.scalars(query.limit(10).offset(30)).all() + assert len(res.page) == 4 + assert res.page == exp diff --git a/tests/test_query.py b/tests/test_query.py index 04dabed..22f9539 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -5,7 +5,8 @@ from sqlalchemy import func from sqlalchemy import not_ -from rqlalchemy import RQLQueryError +from rqlalchemy import RQLSelectError +from rqlalchemy.query import select def to_dict(it): @@ -14,166 +15,165 @@ def to_dict(it): class TestQuery: def test_simple_sort(self, session): - res = session.query(User).rql("sort(balance)").rql_all() - exp = session.query(User).order_by(User.balance).all() + res = select(User).rql("sort(balance)").rql_all(session) + exp = session.scalars(select(User).order_by(User.balance)).all() assert res assert res == exp def test_simple_sort_desc(self, session): - res = session.query(User).rql("sort(-balance)").rql_all() - exp = session.query(User).order_by(User.balance.desc()).all() + res = select(User).rql("sort(-balance)").rql_all(session) + exp = session.scalars(select(User).order_by(User.balance.desc())).all() assert res assert res == exp def test_complex_sort(self, session): - res = session.query(User).rql("sort(balance,registered,birthdate)").rql_all() - exp = session.query(User).order_by(User.balance, User.registered, User.birthdate).all() + res = select(User).rql("sort(balance,registered,birthdate)").rql_all(session) + exp = session.scalars(select(User).order_by(User.balance, User.registered, User.birthdate)).all() assert res assert res == exp def test_in_operator(self, session): - res = session.query(User).rql("in(state,(FL,TX))").rql_all() - exp = session.query(User).filter(User.state.in_(["FL", "TX"])).all() + res = select(User).rql("in(state,(FL,TX))").rql_all(session) + exp = session.scalars(select(User).filter(User.state.in_(["FL", "TX"]))).all() assert res assert res == exp def test_out_operator(self, session): - res = session.query(User).rql("out(state,(FL,TX))").rql_all() - exp = session.query(User).filter(not_(User.state.in_(["FL", "TX"]))).all() + res = select(User).rql("out(state,(FL,TX))").rql_all(session) + exp = session.scalars(select(User).filter(not_(User.state.in_(["FL", "TX"])))).all() assert res assert res == exp def test_contains_string(self, session): - res = session.query(User).rql("contains(email,besto.com)").rql_all() - exp = session.query(User).filter(User.email.contains("besto.com")).all() + res = select(User).rql("contains(email,besto.com)").rql_all(session) + exp = session.scalars(select(User).filter(User.email.contains("besto.com"))).all() assert res assert res == exp def test_excludes_string(self, session): - res = session.query(User).rql("excludes(email,besto.com)").rql_all() - exp = session.query(User).filter(not_(User.email.contains("besto.com"))).all() + res = select(User).rql("excludes(email,besto.com)").rql_all(session) + exp = session.scalars(select(User).filter(not_(User.email.contains("besto.com")))).all() assert res assert res == exp def test_contains_array(self, session): - res = session.query(User).rql("contains(tags,aliqua)").rql_all() - exp = session.query(User).filter(User.tags.contains("aliqua")).all() + res = select(User).rql("contains(tags,aliqua)").rql_all(session) + exp = session.scalars(select(User).filter(User.tags.contains("aliqua"))).all() assert res assert res == exp def test_excludes_array(self, session): - res = session.query(User).rql("excludes(tags,aliqua)").rql_all() - exp = session.query(User).filter(not_(User.tags.contains("aliqua"))).all() + res = select(User).rql("excludes(tags,aliqua)").rql_all(session) + exp = session.scalars(select(User).filter(not_(User.tags.contains("aliqua")))).all() assert res assert res == exp def test_limit(self, session): - res = session.query(User).rql("limit(2)").rql_all() - exp = session.query(User).limit(2).all() + res = select(User).rql("limit(2)").rql_all(session) + exp = session.scalars(select(User).limit(2)).all() assert res assert res == exp def test_select(self, session): - rql_res = session.query(User).rql("select(user_id,state)").rql_all() - res = to_dict(session.query(User.user_id, User.state)) + rql_res = select(User).rql("select(user_id,state)").rql_all(session) + res = to_dict(session.execute(select(User.user_id, User.state))) assert res assert rql_res == res def test_values(self, session): - res = session.query(User).rql("values(state)").rql_all() - exp = [v[0] for v in session.query(User.state)] + res = select(User).rql("values(state)").rql_all(session) + exp = [v[0] for v in session.execute(select(User.state))] assert res assert res == exp def test_sum(self, session): - res = session.query(User).rql("sum(balance)").rql_all() - exp = [session.query(func.sum(User.balance)).scalar()] - assert len(res) == 1 + res = select(User).rql("sum(balance)").rql_all(session) + exp = session.scalar(select(func.sum(User.balance))) assert res == exp def test_mean(self, session): - res = session.query(User).rql("mean(balance)").rql_all() - exp = session.query(func.avg(User.balance)).scalar() + res = select(User).rql("mean(balance)").rql_all(session) + exp = session.scalar(select(func.avg(User.balance))) assert res == exp def test_max(self, session): - res = session.query(User).rql("max(balance)").rql_all() - exp = session.query(func.max(User.balance)).scalar() + res = select(User).rql("max(balance)").rql_all(session) + exp = session.scalar(select(func.max(User.balance))) assert res == exp def test_min(self, session): - res = session.query(User).rql("min(balance)").rql_all() - exp = session.query(func.min(User.balance)).scalar() + res = select(User).rql("min(balance)").rql_all(session) + exp = session.scalar(select(func.min(User.balance))) assert res == exp def test_first(self, session): - res = session.query(User).rql("first()").rql_all() - exp = [session.query(User).first()] + res = select(User).rql("first()").rql_all(session) + exp = [session.scalars(select(User)).first()] assert len(res) == 1 assert res == exp def test_one(self, session): - res = session.query(User).rql("guid=658c407c-6c19-470e-9aa6-8c2b86cddb4b&one()").rql_all() - exp = [session.query(User).filter(User.guid == "658c407c-6c19-470e-9aa6-8c2b86cddb4b").one()] + res = select(User).rql("guid=658c407c-6c19-470e-9aa6-8c2b86cddb4b&one()").rql_all(session) + exp = [session.scalars(select(User).filter(User.guid == "658c407c-6c19-470e-9aa6-8c2b86cddb4b")).one()] assert len(res) == 1 assert res == exp def test_one_no_results_found(self, session): - with pytest.raises(RQLQueryError) as exc: - session.query(User).rql("guid=lero&one()").rql_all() + with pytest.raises(RQLSelectError) as exc: + select(User).rql("guid=lero&one()").rql_all(session) assert exc.value.args[0] == "No result found for one()" def test_one_multiple_results_found(self, session): - with pytest.raises(RQLQueryError) as exc: - session.query(User).rql("state=FL&one()").rql_all() + with pytest.raises(RQLSelectError) as exc: + select(User).rql("state=FL&one()").rql_all(session) assert exc.value.args[0] == "Multiple results found for one()" def test_distinct(self, session): - res = session.query(User).rql("select(gender)&distinct()").rql_all() - exp = to_dict(session.query(User.gender).distinct()) + res = select(User).rql("select(gender)&distinct()").rql_all(session) + exp = to_dict(session.execute(select(User.gender).distinct())) assert len(res) == 2 assert res == exp def test_count(self, session): - res = session.query(User).rql("count()").rql_all() - exp = session.query(User).count() + res = select(User).rql("count()").rql_all(session) + exp = session.scalar(select(func.count()).select_from(User)) assert res == exp @pytest.mark.parametrize("user_id", (1, 2, 3)) def test_eq_operator(self, session, user_id): - res = session.query(User).rql("user_id={}".format(user_id)).rql_all() + res = select(User).rql("user_id={}".format(user_id)).rql_all(session) assert res - assert session.query(User).get(user_id).name == res[0].name + assert session.scalar(select(User).filter_by(user_id=user_id)).name == res[0].name @pytest.mark.parametrize("balance", (1000, 2000, 3000)) def test_gt_operator(self, session, balance): - res = session.query(User).rql("gt(balance,{})".format(balance)).rql_all() + res = select(User).rql("gt(balance,{})".format(balance)).rql_all(session) assert res assert all([u.balance > balance for u in res]) def test_aggregate(self, session): - res = session.query(User).rql("aggregate(state,sum(balance))").rql_all() - exp = to_dict(session.query(User.state, func.sum(User.balance).label("sum")).group_by(User.state).all()) + res = select(User).rql("aggregate(state,sum(balance))").rql_all(session) + exp = to_dict(session.execute(select(User.state, func.sum(User.balance).label("sum")).group_by(User.state)).all()) assert res assert res == exp def test_aggregate_count(self, session): - res = session.query(User).rql("aggregate(gender,count(user_id))").rql_all() - exp = to_dict(session.query(User.gender, func.count(User.user_id).label("count")).group_by(User.gender).all()) + res = select(User).rql("aggregate(gender,count(user_id))").rql_all(session) + exp = to_dict(session.execute(select(User.gender, func.count(User.user_id).label("count")).group_by(User.gender)).all()) assert res assert res == exp def test_aggregate_with_filter(self, session): - res = session.query(User).rql("aggregate(state,sum(balance))&is_active=true").rql_all() - exp = to_dict( - session.query(User.state, func.sum(User.balance).label("sum")) + res = select(User).rql("aggregate(state,sum(balance))&is_active=true").rql_all(session) + exp = to_dict(session.execute( + select(User.state, func.sum(User.balance).label("sum")) .filter(User.is_active == True) - .group_by(User.state) + .group_by(User.state)) .all() ) @@ -181,11 +181,11 @@ def test_aggregate_with_filter(self, session): assert res == exp def test_like_with_relationship_1_deep(self, session, blogs): - res = session.query(User).rql("like((blogs, title), *1*)").rql_all() - exp = session.query(User).join(Blog).filter(Blog.title.like("%1%")).all() + res = select(User).rql("like((blogs, title), *1*)").rql_all(session) + exp = session.scalars(select(User).join(Blog).filter(Blog.title.like("%1%"))).all() assert res == exp def test_like_with_relationship_2_deep(self, session, posts): - res = session.query(User).rql("like((blogs, posts, title), *Post 1*)").rql_all() - exp = session.query(User).join(Blog).join(Post).filter(Post.title.like("%Post 1%")).all() + res = select(User).rql("like((blogs, posts, title), *Post 1*)").rql_all(session) + exp = session.scalars(select(User).join(Blog).join(Post).filter(Post.title.like("%Post 1%"))).all() assert res == exp diff --git a/tests/test_query_defaults.py b/tests/test_query_defaults.py index 2248b58..18aaf16 100644 --- a/tests/test_query_defaults.py +++ b/tests/test_query_defaults.py @@ -3,21 +3,22 @@ from unittest.mock import patch from fixtures import User +from rqlalchemy.query import select class TestQueryDefaults: - @patch("fixtures.RQLQuery._rql_default_limit", 10) + @patch("rqlalchemy.RQLSelect._rql_default_limit", 10) def test_default_limit(self, session): - res = session.query(User).rql("").all() + res = session.scalars(select(User).rql("")).all() assert len(res) == 10 - @patch("fixtures.RQLQuery._rql_max_limit", 100) + @patch("rqlalchemy.RQLSelect._rql_max_limit", 100) def test_max_limit(self, session): - res = session.query(User).rql("limit(110)").all() + res = session.scalars(select(User).rql("limit(110)")).all() assert len(res) == 100 def test_escaped_querystring(self, session): - res = session.query(User).rql("email=mavischerry%40nspire.com").all() - exp = session.query(User).filter(User.email == "mavischerry@nspire.com").all() + res = session.scalars(select(User).rql("email=mavischerry%40nspire.com")).all() + exp = session.scalars(select(User).filter(User.email == "mavischerry@nspire.com")).all() assert res assert res == exp