diff --git a/poetry.lock b/poetry.lock index 073e5cf7..da15ff9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "aiobotocore" @@ -6,6 +6,7 @@ version = "2.13.3" description = "Async client for aws services using botocore and aiohttp" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiobotocore-2.13.3-py3-none-any.whl", hash = "sha256:1272f765fd9414e1a68f8add71978367db94e17e36c3bf629cf1153eb5141fb9"}, {file = "aiobotocore-2.13.3.tar.gz", hash = "sha256:ac5620f93cc3e7c2aef7c67ba2bb74035ff8d49ee2325821daed13b3dd82a473"}, @@ -27,6 +28,7 @@ version = "24.1.0" description = "File support for asyncio." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, @@ -38,6 +40,7 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -49,6 +52,7 @@ version = "3.11.10" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohttp-3.11.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cbad88a61fa743c5d283ad501b01c153820734118b65aee2bd7dbb735475ce0d"}, {file = "aiohttp-3.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80886dac673ceaef499de2f393fc80bb4481a129e6cb29e624a12e3296cc088f"}, @@ -146,6 +150,7 @@ version = "0.12.0" description = "itertools and builtins for AsyncIO and mixed iterables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796"}, {file = "aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b"}, @@ -161,6 +166,7 @@ version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, @@ -169,30 +175,13 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" -[[package]] -name = "aiosqlite" -version = "0.20.0" -description = "asyncio bridge to the standard sqlite3 module" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, - {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, -] - -[package.dependencies] -typing_extensions = ">=4.0" - -[package.extras] -dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] - [[package]] name = "alembic" version = "1.14.0" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"}, {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"}, @@ -212,6 +201,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -223,6 +213,7 @@ version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main", "load-testing"] files = [ {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, @@ -244,6 +235,7 @@ version = "2.0.4" description = "Simple LRU cache for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, @@ -255,80 +247,19 @@ version = "0.2.2" description = "Python decorator for async properties." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"}, {file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"}, ] -[[package]] -name = "asyncpg" -version = "0.30.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"}, - {file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"}, - {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f"}, - {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af"}, - {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75"}, - {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f"}, - {file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf"}, - {file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50"}, - {file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a"}, - {file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed"}, - {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a"}, - {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956"}, - {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056"}, - {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454"}, - {file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d"}, - {file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f"}, - {file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e"}, - {file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a"}, - {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3"}, - {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737"}, - {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a"}, - {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af"}, - {file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e"}, - {file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305"}, - {file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70"}, - {file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3"}, - {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33"}, - {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4"}, - {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4"}, - {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba"}, - {file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590"}, - {file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e"}, - {file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d"}, - {file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168"}, - {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb"}, - {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f"}, - {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38"}, - {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34"}, - {file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4"}, - {file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b"}, - {file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad"}, - {file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff"}, - {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708"}, - {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144"}, - {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb"}, - {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547"}, - {file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a"}, - {file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773"}, - {file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851"}, -] - -[package.extras] -docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] -gssauth = ["gssapi", "sspilib"] -test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] - [[package]] name = "attrs" version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, @@ -348,6 +279,7 @@ version = "24.8.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["linters"] files = [ {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, @@ -392,6 +324,7 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -403,6 +336,7 @@ version = "1.34.162" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "boto3-1.34.162-py3-none-any.whl", hash = "sha256:d6f6096bdab35a0c0deff469563b87d184a28df7689790f7fe7be98502b7c590"}, {file = "boto3-1.34.162.tar.gz", hash = "sha256:873f8f5d2f6f85f1018cbb0535b03cceddc7b655b61f66a0a56995238804f41f"}, @@ -422,6 +356,7 @@ version = "1.34.162" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "botocore-1.34.162-py3-none-any.whl", hash = "sha256:2d918b02db88d27a75b48275e6fb2506e9adaaddbec1ffa6a8a0898b34e769be"}, {file = "botocore-1.34.162.tar.gz", hash = "sha256:adc23be4fb99ad31961236342b7cbf3c0bfc62532cd02852196032e8c0d682f3"}, @@ -441,6 +376,7 @@ version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" +groups = ["load-testing"] files = [ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, @@ -575,6 +511,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "load-testing"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -586,6 +523,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -655,6 +593,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "platform_python_implementation != \"PyPy\"", load-testing = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or implementation_name == \"pypy\""} [package.dependencies] pycparser = "*" @@ -665,6 +604,7 @@ version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "load-testing"] files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, @@ -779,6 +719,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "linters", "load-testing"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -793,10 +734,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "linters", "load-testing"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\"", linters = "platform_system == \"Windows\"", load-testing = "platform_system == \"Windows\""} [[package]] name = "configargparse" @@ -804,6 +747,7 @@ version = "1.7" description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." optional = false python-versions = ">=3.5" +groups = ["load-testing"] files = [ {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, @@ -819,6 +763,7 @@ version = "1.3.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, @@ -892,6 +837,7 @@ version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, @@ -966,6 +912,7 @@ version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] files = [ {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, @@ -1015,6 +962,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -1030,6 +978,7 @@ version = "2.1.0" description = "A library to handle automated deprecations" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, @@ -1044,6 +993,7 @@ version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, @@ -1064,6 +1014,7 @@ version = "3.1.0" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, @@ -1086,6 +1037,7 @@ version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" +groups = ["load-testing"] files = [ {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, @@ -1100,6 +1052,7 @@ version = "0.6.3" description = "User authentication and session management for Flask." optional = false python-versions = ">=3.7" +groups = ["load-testing"] files = [ {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"}, {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"}, @@ -1115,6 +1068,7 @@ version = "4.55.3" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0"}, {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f"}, @@ -1188,6 +1142,7 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -1289,6 +1244,7 @@ version = "2024.10.0" description = "File-system specification" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871"}, {file = "fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493"}, @@ -1328,6 +1284,7 @@ version = "24.11.1" description = "Coroutine-based network library" optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "gevent-24.11.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:92fe5dfee4e671c74ffaa431fd7ffd0ebb4b339363d24d0d944de532409b935e"}, {file = "gevent-24.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7bfcfe08d038e1fa6de458891bca65c1ada6d145474274285822896a858c870"}, @@ -1388,6 +1345,7 @@ version = "2.3.3" description = "HTTP client library for gevent" optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "geventhttpclient-2.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d61cad95f80d5bd599e28933c187b3c4eeb0b2f6306e06fa0edcac5c9c4bac0a"}, {file = "geventhttpclient-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a00e130577c0cf9749d1143e71543c50c7103321b7f37afc42782ad1d3c0ef7"}, @@ -1486,6 +1444,7 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "load-testing"] files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -1561,6 +1520,7 @@ files = [ {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] +markers = {main = "python_version < \"3.13\" 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\")", dev = "python_version < \"3.13\" 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\")", load-testing = "platform_python_implementation == \"CPython\""} [package.extras] docs = ["Sphinx", "furo"] @@ -1572,6 +1532,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "load-testing"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -1583,6 +1544,7 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -1604,6 +1566,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1628,6 +1591,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "load-testing"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1642,6 +1606,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -1653,6 +1618,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["load-testing"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -1664,6 +1630,7 @@ version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["load-testing"] files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, @@ -1681,6 +1648,7 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1692,6 +1660,7 @@ version = "1.5.6" description = "Implementation of JOSE Web standards" optional = false python-versions = ">= 3.8" +groups = ["main"] files = [ {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, @@ -1707,6 +1676,7 @@ version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, @@ -1830,6 +1800,7 @@ version = "2.32.8" description = "Developer-friendly load testing framework" optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "locust-2.32.8-py3-none-any.whl", hash = "sha256:782ccc25e576c4af328ca40a12803b556f6ccc3ad3b073b8074e47b52049ae4b"}, {file = "locust-2.32.8.tar.gz", hash = "sha256:45904026bbe26471876e3f39ecab5403512491638d3974ed159b83e32e2c0f92"}, @@ -1859,6 +1830,7 @@ version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, @@ -1878,6 +1850,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main", "dev", "load-testing"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1948,6 +1921,7 @@ version = "3.9.4" description = "Python plotting package" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, @@ -2012,6 +1986,7 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["load-testing"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -2085,6 +2060,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -2186,6 +2162,7 @@ version = "1.12" description = "Multiple argument dispatching." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "multimethod-1.12-py3-none-any.whl", hash = "sha256:fd0c473c43558908d97cc06e4d68e8f69202f167db46f7b4e4058893e7dbdf60"}, {file = "multimethod-1.12.tar.gz", hash = "sha256:8db8ef2a8d2a247e3570cc23317680892fdf903d84c8c1053667c8e8f7671a67"}, @@ -2197,6 +2174,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["main", "linters"] 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"}, @@ -2208,6 +2186,7 @@ version = "2.2.0" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"}, {file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"}, @@ -2272,6 +2251,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "linters"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -2283,6 +2263,7 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -2365,6 +2346,7 @@ version = "0.21.1" description = "A light-weight and flexible data validation and testing tool for statistical data objects." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pandera-0.21.1-py3-none-any.whl", hash = "sha256:cb6323952815ab82484bd8371f71d0b609a9cd0f515a7b91b2c076871b4db387"}, {file = "pandera-0.21.1.tar.gz", hash = "sha256:3a40b643cd32d1fdd4142917ede1ae91b93a5f3469b01fcf70ffd1046964818c"}, @@ -2401,6 +2383,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["linters"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2412,6 +2395,7 @@ version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, @@ -2504,6 +2488,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["linters"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -2520,6 +2505,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2535,6 +2521,7 @@ version = "1.17.1" description = "Blazingly fast DataFrame library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "polars_lts_cpu-1.17.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:abdaf10b6b533bd16674bdb3355ee1557fcb0c7bfe2010d77820cee6302f7318"}, {file = "polars_lts_cpu-1.17.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:329c420767e9cc0317e5d530df0e79de7082e59c82e00a305a703fa07bb42749"}, @@ -2576,6 +2563,7 @@ version = "0.2.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, @@ -2667,6 +2655,7 @@ version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["load-testing"] files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -2697,6 +2686,7 @@ version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, @@ -2774,6 +2764,7 @@ version = "18.1.0" description = "Python library for Apache Arrow" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c"}, {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4"}, @@ -2828,10 +2819,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "platform_python_implementation != \"PyPy\"", load-testing = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or implementation_name == \"pypy\""} [[package]] name = "pydantic" @@ -2839,6 +2832,7 @@ version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, @@ -2859,6 +2853,7 @@ version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, @@ -2971,6 +2966,7 @@ version = "2.7.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, @@ -2991,6 +2987,7 @@ version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, @@ -3008,6 +3005,7 @@ version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, @@ -3022,6 +3020,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -3042,6 +3041,7 @@ version = "0.11.1" description = "A pytest plugin for verifying alembic migrations." optional = false python-versions = "<4,>=3.6" +groups = ["dev"] files = [ {file = "pytest_alembic-0.11.1-py3-none-any.whl", hash = "sha256:f83e8c1534d50ced053aa4b1dbf6e261f4674aa626cb852fc1dcb565049ae152"}, {file = "pytest_alembic-0.11.1.tar.gz", hash = "sha256:a920d8770b5be77326c5c1b2bd8d4d4a0dd8fc2c2d57abbcd1fec28a21131b85"}, @@ -3058,6 +3058,7 @@ version = "0.25.3" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, @@ -3076,6 +3077,7 @@ version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, @@ -3094,6 +3096,7 @@ version = "1.1.5" description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, @@ -3111,6 +3114,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -3128,6 +3132,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -3142,6 +3147,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -3156,6 +3162,7 @@ version = "5.2.0" description = "python-keycloak is a Python package providing access to the Keycloak API." optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ {file = "python_keycloak-5.2.0-py3-none-any.whl", hash = "sha256:fed3e5ab809e38580566e0595896ef8443b5e59bbfdc2cb41c84b8dd48194462"}, {file = "python_keycloak-5.2.0.tar.gz", hash = "sha256:d7fee176d17c8748445f7e7dfe9f37077dc62ca207815cebfcff280db5c42bc1"}, @@ -3176,6 +3183,7 @@ version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, @@ -3187,6 +3195,7 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -3198,6 +3207,8 @@ version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["load-testing"] +markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -3225,6 +3236,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -3287,6 +3299,7 @@ version = "26.2.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" +groups = ["load-testing"] files = [ {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, @@ -3408,6 +3421,7 @@ version = "0.1.0" description = "" optional = false python-versions = "^3.12" +groups = ["main"] files = [] develop = false @@ -3432,6 +3446,7 @@ version = "0.1.0" description = "RegTech submission data parser and validator" optional = false python-versions = ">=3.12,<4" +groups = ["main"] files = [] develop = false @@ -3460,6 +3475,7 @@ version = "0.1.0" description = "" optional = false python-versions = ">=3.12,<4" +groups = ["main"] files = [] develop = false @@ -3478,6 +3494,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -3499,6 +3516,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3513,6 +3531,7 @@ version = "0.9.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["linters"] files = [ {file = "ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706"}, {file = "ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf"}, @@ -3540,6 +3559,7 @@ version = "2024.10.0" description = "Convenient Filesystem interface over S3" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "s3fs-2024.10.0-py3-none-any.whl", hash = "sha256:7a2025d60d5b1a6025726b3a5e292a8e5aa713abc3b16fd1f81735181f7bb282"}, {file = "s3fs-2024.10.0.tar.gz", hash = "sha256:58b8c3650f8b99dbedf361543da3533aac8707035a104db5d80b094617ad4a3f"}, @@ -3560,6 +3580,7 @@ version = "0.10.4" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, @@ -3577,6 +3598,7 @@ version = "75.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, @@ -3597,6 +3619,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3608,6 +3631,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "load-testing"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3619,6 +3643,7 @@ version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, @@ -3714,6 +3739,7 @@ version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, @@ -3731,6 +3757,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -3745,6 +3772,7 @@ version = "4.4.1" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typeguard-4.4.1-py3-none-any.whl", hash = "sha256:9324ec07a27ec67fc54a9c063020ca4c0ae6abad5e9f0f9804ca59aee68c6e21"}, {file = "typeguard-4.4.1.tar.gz", hash = "sha256:0d22a89d00b453b47c49875f42b6601b961757541a2e1e0ef517b6e24213c21b"}, @@ -3763,10 +3791,12 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "load-testing"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +markers = {load-testing = "python_version < \"3.13\""} [[package]] name = "typing-inspect" @@ -3774,6 +3804,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -3789,6 +3820,7 @@ version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, @@ -3800,6 +3832,7 @@ version = "5.10.0" description = "Ultra fast JSON encoder and decoder for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, @@ -3887,6 +3920,7 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "load-testing"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -3904,6 +3938,7 @@ version = "0.32.1" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"}, {file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"}, @@ -3922,6 +3957,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["load-testing"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -3939,6 +3975,7 @@ version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, @@ -4013,6 +4050,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -4109,6 +4147,7 @@ version = "5.0" description = "Very basic event publishing system" optional = false python-versions = ">=3.7" +groups = ["load-testing"] files = [ {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, @@ -4127,6 +4166,7 @@ version = "7.2" description = "Interfaces for Python" optional = false python-versions = ">=3.8" +groups = ["load-testing"] files = [ {file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"}, {file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"}, @@ -4176,6 +4216,6 @@ test = ["coverage[toml]", "zope.event", "zope.testing"] testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.12,<4" -content-hash = "a85c0ace17c60bfd7a61a4f8d152da25953fe27d268357f4196d07835d515885" +content-hash = "4c7dc0034d3c35d3a4146d0af02e9c581679b97e0479fcc0bb7301f5dd173fbf" diff --git a/pyproject.toml b/pyproject.toml index c52d3658..edac0577 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ packages = [{ include = "sbl_filing_api", from = "src" }] python = ">=3.12,<4" sqlalchemy = "^2.0.35" psycopg2-binary = "^2.9.9" -asyncpg = "^0.30.0" regtech-api-commons = {git = "https://github.com/cfpb/regtech-api-commons.git"} regtech-data-validator = {git = "https://github.com/cfpb/regtech-data-validator.git"} regtech-regex = {git = "https://github.com/cfpb/regtech-regex.git"} @@ -27,7 +26,6 @@ pytest-mock = "^3.12.0" pytest-env = "^1.1.5" pytest-alembic = "^0.11.1" pytest-asyncio = "^0.25.3" -aiosqlite = "^0.20.0" [tool.poetry.group.linters.dependencies] diff --git a/src/.env.template b/src/.env.template index 0fb05ab1..bed07efc 100644 --- a/src/.env.template +++ b/src/.env.template @@ -3,7 +3,7 @@ DB_USER= DB_PWD= DB_HOST= DB_SCHEMA= -# DB_SCHEME= can be used to override postgresql+asyncpg if needed +# DB_SCHEME= can be used to override postgresql+psycopg2 if needed KC_URL= KC_REALM= KC_ADMIN_CLIENT_ID= diff --git a/src/sbl_filing_api/config.py b/src/sbl_filing_api/config.py index 0309de73..ad679bec 100644 --- a/src/sbl_filing_api/config.py +++ b/src/sbl_filing_api/config.py @@ -44,7 +44,7 @@ class Settings(BaseSettings): db_user: str db_pwd: str db_host: str - db_scheme: str = "postgresql+asyncpg" + db_scheme: str = "postgresql+psycopg2" db_logging: bool = False conn: PostgresDsn | None = None diff --git a/src/sbl_filing_api/entities/engine/engine.py b/src/sbl_filing_api/entities/engine/engine.py index 474dc43c..4fa56aa8 100644 --- a/src/sbl_filing_api/entities/engine/engine.py +++ b/src/sbl_filing_api/entities/engine/engine.py @@ -1,21 +1,17 @@ -from sqlalchemy.ext.asyncio import ( - create_async_engine, - async_sessionmaker, - async_scoped_session, -) +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.pool import NullPool -from asyncio import current_task from sbl_filing_api.config import settings -engine = create_async_engine( - settings.conn.unicode_string(), echo=settings.db_logging, poolclass=NullPool -).execution_options(schema_translate_map={None: settings.db_schema}) -SessionLocal = async_scoped_session(async_sessionmaker(engine, expire_on_commit=False), current_task) +engine = create_engine(settings.conn.unicode_string(), echo=settings.db_logging, poolclass=NullPool).execution_options( + schema_translate_map={None: settings.db_schema} +) +SessionLocal = scoped_session(sessionmaker(engine, expire_on_commit=False)) -async def get_session(): +def get_session(): session = SessionLocal() try: yield session finally: - await session.close() + session.close() diff --git a/src/sbl_filing_api/entities/models/dao.py b/src/sbl_filing_api/entities/models/dao.py index e1593fa5..563abb58 100644 --- a/src/sbl_filing_api/entities/models/dao.py +++ b/src/sbl_filing_api/entities/models/dao.py @@ -4,11 +4,10 @@ from sqlalchemy import Enum as SAEnum, String, desc from sqlalchemy import ForeignKey, func, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship -from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.types import JSON -class Base(AsyncAttrs, DeclarativeBase): +class Base(DeclarativeBase): pass diff --git a/src/sbl_filing_api/entities/repos/submission_repo.py b/src/sbl_filing_api/entities/repos/submission_repo.py index 70580ee2..3e33986c 100644 --- a/src/sbl_filing_api/entities/repos/submission_repo.py +++ b/src/sbl_filing_api/entities/repos/submission_repo.py @@ -1,13 +1,13 @@ import logging from sqlalchemy import select, desc -from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session from typing import Any, List, TypeVar from sbl_filing_api.entities.engine.engine import SessionLocal from regtech_api_commons.models.auth import AuthenticatedUser -from async_lru import alru_cache +from functools import lru_cache from sbl_filing_api.entities.models.dao import ( SubmissionDAO, @@ -31,73 +31,73 @@ class NoFilingPeriodException(Exception): pass -async def get_submissions(session: AsyncSession, lei: str = None, filing_period: str = None) -> List[SubmissionDAO]: +def get_submissions(session: Session, lei: str = None, filing_period: str = None) -> List[SubmissionDAO]: filing_id = None if lei and filing_period: - filing = await get_filing(session, lei=lei, filing_period=filing_period) + filing = get_filing(session, lei=lei, filing_period=filing_period) filing_id = filing.id - return await query_helper(session, SubmissionDAO, filing=filing_id) + return query_helper(session, SubmissionDAO, filing=filing_id) -async def get_latest_submission(session: AsyncSession, lei: str, filing_period: str) -> SubmissionDAO | None: - filing = await get_filing(session, lei=lei, filing_period=filing_period) +def get_latest_submission(session: Session, lei: str, filing_period: str) -> SubmissionDAO | None: + filing = get_filing(session, lei=lei, filing_period=filing_period) stmt = select(SubmissionDAO).filter_by(filing=filing.id).order_by(desc(SubmissionDAO.submission_time)).limit(1) - return await session.scalar(stmt) + return session.scalar(stmt) -async def get_filing_periods(session: AsyncSession) -> List[FilingPeriodDAO]: - return await query_helper(session, FilingPeriodDAO) +def get_filing_periods(session: Session) -> List[FilingPeriodDAO]: + return query_helper(session, FilingPeriodDAO) -async def get_submission(session: AsyncSession, submission_id: int) -> SubmissionDAO: - result = await query_helper(session, SubmissionDAO, id=submission_id) +def get_submission(session: Session, submission_id: int) -> SubmissionDAO: + result = query_helper(session, SubmissionDAO, id=submission_id) return result[0] if result else None -async def get_submission_by_counter(session: AsyncSession, lei: str, filing_period: str, counter: int) -> SubmissionDAO: - filing = await get_filing(session, lei=lei, filing_period=filing_period) - result = await query_helper(session, SubmissionDAO, filing=filing.id, counter=counter) +def get_submission_by_counter(session: Session, lei: str, filing_period: str, counter: int) -> SubmissionDAO: + filing = get_filing(session, lei=lei, filing_period=filing_period) + result = query_helper(session, SubmissionDAO, filing=filing.id, counter=counter) return result[0] if result else None -async def get_filing(session: AsyncSession, lei: str, filing_period: str) -> FilingDAO: - result = await query_helper(session, FilingDAO, lei=lei, filing_period=filing_period) +def get_filing(session: Session, lei: str, filing_period: str) -> FilingDAO: + result = query_helper(session, FilingDAO, lei=lei, filing_period=filing_period) return result[0] if result else None -async def get_filings(session: AsyncSession, leis: list[str], filing_period: str) -> list[FilingDAO]: +def get_filings(session: Session, leis: list[str], filing_period: str) -> list[FilingDAO]: stmt = select(FilingDAO).filter(FilingDAO.lei.in_(leis), FilingDAO.filing_period == filing_period) - result = (await session.scalars(stmt)).all() + result = (session.scalars(stmt)).all() return result if result else [] -async def get_period_filings(session: AsyncSession, filing_period: str) -> List[FilingDAO]: - filings = await query_helper(session, FilingDAO, filing_period=filing_period) +def get_period_filings(session: Session, filing_period: str) -> List[FilingDAO]: + filings = query_helper(session, FilingDAO, filing_period=filing_period) return filings -async def get_filing_period(session: AsyncSession, filing_period: str) -> FilingPeriodDAO: - result = await query_helper(session, FilingPeriodDAO, code=filing_period) +def get_filing_period(session: Session, filing_period: str) -> FilingPeriodDAO: + result = query_helper(session, FilingPeriodDAO, code=filing_period) return result[0] if result else None -@alru_cache(maxsize=128) -async def get_filing_tasks(session: AsyncSession) -> List[FilingTaskDAO]: - return await query_helper(session, FilingTaskDAO) +@lru_cache(maxsize=128) +def get_filing_tasks(session: Session) -> List[FilingTaskDAO]: + return query_helper(session, FilingTaskDAO) -async def get_user_action(session: AsyncSession, id: int) -> UserActionDAO: - result = await query_helper(session, UserActionDAO, id=id) +def get_user_action(session: Session, id: int) -> UserActionDAO: + result = query_helper(session, UserActionDAO, id=id) return result[0] if result else None -async def get_user_actions(session: AsyncSession) -> List[UserActionDAO]: - return await query_helper(session, UserActionDAO) +def get_user_actions(session: Session) -> List[UserActionDAO]: + return query_helper(session, UserActionDAO) -async def add_submission(session: AsyncSession, filing_id: int, filename: str, submitter_id: int) -> SubmissionDAO: +def add_submission(session: Session, filing_id: int, filename: str, submitter_id: int) -> SubmissionDAO: stmt = select(SubmissionDAO).filter_by(filing=filing_id).order_by(desc(SubmissionDAO.counter)).limit(1) - last_sub = await session.scalar(stmt) + last_sub = session.scalar(stmt) current_count = last_sub.counter if last_sub else 0 new_sub = SubmissionDAO( filing=filing_id, @@ -107,93 +107,91 @@ async def add_submission(session: AsyncSession, filing_id: int, filename: str, s counter=(current_count + 1), ) # this returns the attached object, most importantly with the new submission id - new_sub = await session.merge(new_sub) - await session.commit() + new_sub = session.merge(new_sub) + session.commit() return new_sub -async def update_submission(session: AsyncSession, submission: SubmissionDAO) -> SubmissionDAO: - return await upsert_helper(session, submission, SubmissionDAO) +def update_submission(session: Session, submission: SubmissionDAO) -> SubmissionDAO: + return upsert_helper(session, submission, SubmissionDAO) -async def expire_submission(submission_id: int): - async with SessionLocal() as session: - submission = await get_submission(session, submission_id) +def expire_submission(submission_id: int): + with SessionLocal() as session: + submission = get_submission(session, submission_id) submission.state = SubmissionState.VALIDATION_EXPIRED - await upsert_helper(session, submission, SubmissionDAO) + upsert_helper(session, submission, SubmissionDAO) -async def error_out_submission(submission_id: int): - async with SessionLocal() as session: - submission = await get_submission(session, submission_id) +def error_out_submission(submission_id: int): + with SessionLocal() as session: + submission = get_submission(session, submission_id) submission.state = SubmissionState.VALIDATION_ERROR - await upsert_helper(session, submission, SubmissionDAO) + upsert_helper(session, submission, SubmissionDAO) -async def upsert_filing_period(session: AsyncSession, filing_period: FilingPeriodDTO) -> FilingPeriodDAO: - return await upsert_helper(session, filing_period, FilingPeriodDAO) +def upsert_filing_period(session: Session, filing_period: FilingPeriodDTO) -> FilingPeriodDAO: + return upsert_helper(session, filing_period, FilingPeriodDAO) -async def upsert_filing(session: AsyncSession, filing: FilingDTO) -> FilingDAO: - return await upsert_helper(session, filing, FilingDAO) +def upsert_filing(session: Session, filing: FilingDTO) -> FilingDAO: + return upsert_helper(session, filing, FilingDAO) -async def create_new_filing(session: AsyncSession, lei: str, filing_period: str, creator_id: int) -> FilingDAO: +def create_new_filing(session: Session, lei: str, filing_period: str, creator_id: int) -> FilingDAO: new_filing = FilingDAO(filing_period=filing_period, lei=lei, creator_id=creator_id) - return await upsert_helper(session, new_filing, FilingDAO) + return upsert_helper(session, new_filing, FilingDAO) -async def update_task_state( - session: AsyncSession, lei: str, filing_period: str, task_name: str, state: FilingTaskState, user: AuthenticatedUser +def update_task_state( + session: Session, lei: str, filing_period: str, task_name: str, state: FilingTaskState, user: AuthenticatedUser ): - filing = await get_filing(session, lei=lei, filing_period=filing_period) - found_task = await query_helper(session, FilingTaskProgressDAO, filing=filing.id, task_name=task_name) + filing = get_filing(session, lei=lei, filing_period=filing_period) + found_task = query_helper(session, FilingTaskProgressDAO, filing=filing.id, task_name=task_name) if found_task: task = found_task[0] # should only be one task.state = state task.user = user.username else: task = FilingTaskProgressDAO(filing=filing.id, state=state, task_name=task_name, user=user.username) - await upsert_helper(session, task, FilingTaskProgressDAO) + upsert_helper(session, task, FilingTaskProgressDAO) -async def update_contact_info( - session: AsyncSession, lei: str, filing_period: str, new_contact_info: ContactInfoDTO -) -> FilingDAO: - filing = await get_filing(session, lei=lei, filing_period=filing_period) +def update_contact_info(session: Session, lei: str, filing_period: str, new_contact_info: ContactInfoDTO) -> FilingDAO: + filing = get_filing(session, lei=lei, filing_period=filing_period) if filing.contact_info: for key, value in new_contact_info.__dict__.items(): if key != "id": setattr(filing.contact_info, key, value) else: filing.contact_info = ContactInfoDAO(**new_contact_info.__dict__.copy(), filing=filing.id) - return await upsert_helper(session, filing, FilingDAO) + return upsert_helper(session, filing, FilingDAO) -async def add_user_action( - session: AsyncSession, +def add_user_action( + session: Session, new_user_action: UserActionDTO, ) -> UserActionDAO: - return await upsert_helper(session, new_user_action, UserActionDAO) + return upsert_helper(session, new_user_action, UserActionDAO) -async def upsert_helper(session: AsyncSession, original_data: Any, table_obj: T) -> T: +def upsert_helper(session: Session, original_data: Any, table_obj: T) -> T: copy_data = original_data.__dict__.copy() # this is only for if a DAO is passed in # Should be DTOs, but hey, it's python if "_sa_instance_state" in copy_data: del copy_data["_sa_instance_state"] new_dao = table_obj(**copy_data) - new_dao = await session.merge(new_dao) - await session.commit() - await session.refresh(new_dao) + new_dao = session.merge(new_dao) + session.commit() + session.refresh(new_dao) return new_dao -async def query_helper(session: AsyncSession, table_obj: T, **filter_args) -> List[T]: +def query_helper(session: Session, table_obj: T, **filter_args) -> List[T]: stmt = select(table_obj) # remove empty args filter_args = {k: v for k, v in filter_args.items() if v is not None} if filter_args: stmt = stmt.filter_by(**filter_args) - return (await session.scalars(stmt)).all() + return (session.scalars(stmt)).all() diff --git a/src/sbl_filing_api/routers/filing.py b/src/sbl_filing_api/routers/filing.py index 8e954705..a516d29e 100644 --- a/src/sbl_filing_api/routers/filing.py +++ b/src/sbl_filing_api/routers/filing.py @@ -33,7 +33,7 @@ from sbl_filing_api.entities.repos import submission_repo as repo -from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session from starlette.authentication import requires @@ -46,7 +46,7 @@ logger = logging.getLogger(__name__) -async def set_db(request: Request, session: Annotated[AsyncSession, Depends(get_session)]): +def set_db(request: Request, session: Annotated[Session, Depends(get_session)]): request.state.db_session = session @@ -56,14 +56,14 @@ async def set_db(request: Request, session: Annotated[AsyncSession, Depends(get_ @router.get("/periods", response_model=List[FilingPeriodDTO]) @requires("authenticated") -async def get_filing_periods(request: Request): - return await repo.get_filing_periods(request.state.db_session) +def get_filing_periods(request: Request): + return repo.get_filing_periods(request.state.db_session) @router.get("/institutions/{lei}/filings/{period_code}", response_model=FilingDTO | None) @requires("authenticated") -async def get_filing(request: Request, response: Response, lei: str, period_code: str): - res = await repo.get_filing(request.state.db_session, lei, period_code) +def get_filing(request: Request, response: Response, lei: str, period_code: str): + res = repo.get_filing(request.state.db_session, lei, period_code) if res: return res response.status_code = status.HTTP_204_NO_CONTENT @@ -71,9 +71,9 @@ async def get_filing(request: Request, response: Response, lei: str, period_code @router.get("/periods/{period_code}/filings", response_model=List[FilingDTO]) @requires("authenticated") -async def get_filings(request: Request, period_code: str): +def get_filings(request: Request, period_code: str): user: AuthenticatedUser = request.user - return await repo.get_filings(request.state.db_session, user.institutions, period_code) + return repo.get_filings(request.state.db_session, user.institutions, period_code) @router.post( @@ -85,10 +85,10 @@ async def get_filings(request: Request, period_code: str): ], ) @requires("authenticated") -async def post_filing(request: Request, lei: str, period_code: str): +def post_filing(request: Request, lei: str, period_code: str): creator = None try: - creator = await repo.add_user_action( + creator = repo.add_user_action( request.state.db_session, UserActionDTO( user_id=request.user.id, @@ -106,7 +106,7 @@ async def post_filing(request: Request, lei: str, period_code: str): ) try: - return await repo.create_new_filing(request.state.db_session, lei, period_code, creator_id=creator.id) + return repo.create_new_filing(request.state.db_session, lei, period_code, creator_id=creator.id) except Exception: raise RegTechHttpException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -124,10 +124,10 @@ async def post_filing(request: Request, lei: str, period_code: str): ], ) @requires("authenticated") -async def sign_filing(request: Request, lei: str, period_code: str): +def sign_filing(request: Request, lei: str, period_code: str): filing: FilingDAO = request.state.context["filing"] - latest_sub = (await filing.awaitable_attrs.submissions)[0] - sig = await repo.add_user_action( + latest_sub = filing.submissions[0] + sig = repo.add_user_action( request.state.db_session, UserActionDTO( user_id=request.user.id, @@ -142,7 +142,7 @@ async def sign_filing(request: Request, lei: str, period_code: str): send_confirmation_email( request.user.name, request.user.email, filing.contact_info.email, filing.confirmation_id, sig_timestamp ) - return await repo.upsert_filing(request.state.db_session, filing) + return repo.upsert_filing(request.state.db_session, filing) @router.post("/institutions/{lei}/filings/{period_code}/submissions", response_model=SubmissionDTO) @@ -151,7 +151,7 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload submission_processor.validate_file_processable(file) content = await file.read() - filing = await repo.get_filing(request.state.db_session, lei, period_code) + filing = repo.get_filing(request.state.db_session, lei, period_code) if not filing: raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, @@ -160,7 +160,7 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload ) submission = None try: - submitter = await repo.add_user_action( + submitter = repo.add_user_action( request.state.db_session, UserActionDTO( user_id=request.user.id, @@ -169,7 +169,7 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload action_type=UserActionType.SUBMIT, ), ) - submission = await repo.add_submission(request.state.db_session, filing.id, file.filename, submitter.id) + submission = repo.add_submission(request.state.db_session, filing.id, file.filename, submitter.id) try: submission_processor.upload_to_storage( period_code, lei, submission.counter, content, file.filename.split(".")[-1] @@ -179,10 +179,10 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload with io.BytesIO(content) as byte_stream: reader = csv.reader(io.TextIOWrapper(byte_stream)) submission.total_records = sum(1 for row in reader) - 1 - submission = await repo.update_submission(request.state.db_session, submission) + submission = repo.update_submission(request.state.db_session, submission) except Exception as e: submission.state = SubmissionState.UPLOAD_FAILED - submission = await repo.update_submission(request.state.db_session, submission) + submission = repo.update_submission(request.state.db_session, submission) raise RegTechHttpException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, name="Submission Unprocessable", @@ -200,7 +200,7 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload if submission: try: submission.state = SubmissionState.UPLOAD_FAILED - submission = await repo.update_submission(request.state.db_session, submission) + submission = repo.update_submission(request.state.db_session, submission) except Exception as ex: logger.error( ( @@ -220,21 +220,21 @@ async def upload_file(request: Request, lei: str, period_code: str, file: Upload @router.get("/institutions/{lei}/filings/{period_code}/submissions", response_model=List[SubmissionDTO]) @requires("authenticated") -async def get_submissions(request: Request, lei: str, period_code: str): - return await repo.get_submissions(request.state.db_session, lei, period_code) +def get_submissions(request: Request, lei: str, period_code: str): + return repo.get_submissions(request.state.db_session, lei, period_code) @router.get("/institutions/{lei}/filings/{period_code}/submissions/latest", response_model=SubmissionDTO) @requires("authenticated") -async def get_submission_latest(request: Request, lei: str, period_code: str): - filing = await repo.get_filing(request.state.db_session, lei, period_code) +def get_submission_latest(request: Request, lei: str, period_code: str): + filing = repo.get_filing(request.state.db_session, lei, period_code) if not filing: raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, name="Filing Not Found", detail=f"There is no Filing for LEI {lei} in period {period_code}, unable to get latest submission for it.", ) - result = await repo.get_latest_submission(request.state.db_session, lei, period_code) + result = repo.get_latest_submission(request.state.db_session, lei, period_code) if result: return result return Response(status_code=status.HTTP_204_NO_CONTENT) @@ -242,8 +242,8 @@ async def get_submission_latest(request: Request, lei: str, period_code: str): @router.get("/institutions/{lei}/filings/{period_code}/submissions/{counter}", response_model=SubmissionDTO | None) @requires("authenticated") -async def get_submission(request: Request, response: Response, counter: int, lei: str, period_code: str): - result = await repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) +def get_submission(request: Request, response: Response, counter: int, lei: str, period_code: str): + result = repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) if result: return result response.status_code = status.HTTP_404_NOT_FOUND @@ -251,8 +251,8 @@ async def get_submission(request: Request, response: Response, counter: int, lei @router.put("/institutions/{lei}/filings/{period_code}/submissions/{counter}/accept", response_model=SubmissionDTO) @requires("authenticated") -async def accept_submission(request: Request, counter: int, lei: str, period_code: str): - submission = await repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) +def accept_submission(request: Request, counter: int, lei: str, period_code: str): + submission = repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) if not submission: raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, @@ -269,7 +269,7 @@ async def accept_submission(request: Request, counter: int, lei: str, period_cod detail=f"Submission {counter} for LEI {lei} in filing period {period_code} is not in an acceptable state. Submissions must be validated successfully or with only warnings to be accepted.", ) - accepter = await repo.add_user_action( + accepter = repo.add_user_action( request.state.db_session, UserActionDTO( user_id=request.user.id, @@ -281,17 +281,17 @@ async def accept_submission(request: Request, counter: int, lei: str, period_cod submission.accepter_id = accepter.id submission.state = SubmissionState.SUBMISSION_ACCEPTED - submission = await repo.update_submission(request.state.db_session, submission) + submission = repo.update_submission(request.state.db_session, submission) return submission @router.put("/institutions/{lei}/filings/{period_code}/institution-snapshot-id", response_model=FilingDTO) @requires("authenticated") -async def put_institution_snapshot(request: Request, lei: str, period_code: str, update_value: SnapshotUpdateDTO): - result = await repo.get_filing(request.state.db_session, lei, period_code) +def put_institution_snapshot(request: Request, lei: str, period_code: str, update_value: SnapshotUpdateDTO): + result = repo.get_filing(request.state.db_session, lei, period_code) if result: result.institution_snapshot_id = update_value.institution_snapshot_id - return await repo.upsert_filing(request.state.db_session, result) + return repo.upsert_filing(request.state.db_session, result) raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, name="Filing Not Found", @@ -301,14 +301,14 @@ async def put_institution_snapshot(request: Request, lei: str, period_code: str, @router.post("/institutions/{lei}/filings/{period_code}/tasks/{task_name}", deprecated=True) @requires("authenticated") -async def update_task_state(request: Request, lei: str, period_code: str, task_name: str, state: StateUpdateDTO): - await repo.update_task_state(request.state.db_session, lei, period_code, task_name, state.state, request.user) +def update_task_state(request: Request, lei: str, period_code: str, task_name: str, state: StateUpdateDTO): + repo.update_task_state(request.state.db_session, lei, period_code, task_name, state.state, request.user) @router.get("/institutions/{lei}/filings/{period_code}/contact-info", response_model=ContactInfoDTO | None) @requires("authenticated") -async def get_contact_info(request: Request, response: Response, lei: str, period_code: str): - filing = await repo.get_filing(request.state.db_session, lei, period_code) +def get_contact_info(request: Request, response: Response, lei: str, period_code: str): + filing = repo.get_filing(request.state.db_session, lei, period_code) if filing and filing.contact_info: return filing.contact_info response.status_code = status.HTTP_404_NOT_FOUND @@ -316,10 +316,10 @@ async def get_contact_info(request: Request, response: Response, lei: str, perio @router.put("/institutions/{lei}/filings/{period_code}/contact-info", response_model=FilingDTO) @requires("authenticated") -async def put_contact_info(request: Request, lei: str, period_code: str, contact_info: ContactInfoDTO): - result = await repo.get_filing(request.state.db_session, lei, period_code) +def put_contact_info(request: Request, lei: str, period_code: str, contact_info: ContactInfoDTO): + result = repo.get_filing(request.state.db_session, lei, period_code) if result: - return await repo.update_contact_info(request.state.db_session, lei, period_code, contact_info) + return repo.update_contact_info(request.state.db_session, lei, period_code, contact_info) raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, name="Filing Not Found", @@ -332,15 +332,15 @@ async def put_contact_info(request: Request, lei: str, period_code: str, contact responses={200: {"content": {"text/plain; charset=utf-8": {}}}}, ) @requires("authenticated") -async def get_latest_submission_report(request: Request, lei: str, period_code: str): - filing = await repo.get_filing(request.state.db_session, lei, period_code) +def get_latest_submission_report(request: Request, lei: str, period_code: str): + filing = repo.get_filing(request.state.db_session, lei, period_code) if not filing: raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, name="Filing Not Found", detail=f"There is no Filing for LEI {lei} in period {period_code}, unable to get latest submission for it.", ) - latest_sub = await repo.get_latest_submission(request.state.db_session, lei, period_code) + latest_sub = repo.get_latest_submission(request.state.db_session, lei, period_code) if latest_sub and latest_sub.state in [ SubmissionState.VALIDATION_SUCCESSFUL, SubmissionState.VALIDATION_WITH_ERRORS, @@ -371,8 +371,8 @@ async def get_latest_submission_report(request: Request, lei: str, period_code: responses={200: {"content": {"text/plain; charset=utf-8": {}}}}, ) @requires("authenticated") -async def get_submission_report(request: Request, response: Response, lei: str, period_code: str, counter: int): - sub = await repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) +def get_submission_report(request: Request, response: Response, lei: str, period_code: str, counter: int): + sub = repo.get_submission_by_counter(request.state.db_session, lei, period_code, counter) if sub and sub.state in [ SubmissionState.VALIDATION_SUCCESSFUL, SubmissionState.VALIDATION_WITH_ERRORS, @@ -400,11 +400,11 @@ async def get_submission_report(request: Request, response: Response, lei: str, @router.put("/institutions/{lei}/filings/{period_code}/is-voluntary", response_model=FilingDTO) @requires("authenticated") -async def update_is_voluntary(request: Request, lei: str, period_code: str, update_value: VoluntaryUpdateDTO): - result = await repo.get_filing(request.state.db_session, lei, period_code) +def update_is_voluntary(request: Request, lei: str, period_code: str, update_value: VoluntaryUpdateDTO): + result = repo.get_filing(request.state.db_session, lei, period_code) if result: result.is_voluntary = update_value.is_voluntary - res = await repo.upsert_filing(request.state.db_session, result) + res = repo.upsert_filing(request.state.db_session, result) return res raise RegTechHttpException( status_code=status.HTTP_404_NOT_FOUND, diff --git a/src/sbl_filing_api/services/multithread_handler.py b/src/sbl_filing_api/services/multithread_handler.py index f902af74..e3d8063a 100644 --- a/src/sbl_filing_api/services/multithread_handler.py +++ b/src/sbl_filing_api/services/multithread_handler.py @@ -26,13 +26,13 @@ async def check_future(future, submission_id, exec_check): except asyncio.InvalidStateError: future.cancel() exec_check["continue"] = False - await repo.expire_submission(submission_id) + repo.expire_submission(submission_id) logger.warning( f"Validation for submission {submission_id} did not complete within the expected timeframe, will be set to VALIDATION_EXPIRED." ) except Exception: exec_check["continue"] = False - await repo.error_out_submission(submission_id) + repo.error_out_submission(submission_id) logger.error( f"Validation for submission {submission_id} did not complete due to an unexpected error.", exc_info=True, diff --git a/src/sbl_filing_api/services/request_action_validator.py b/src/sbl_filing_api/services/request_action_validator.py index 2723e032..bd02835d 100644 --- a/src/sbl_filing_api/services/request_action_validator.py +++ b/src/sbl_filing_api/services/request_action_validator.py @@ -79,11 +79,9 @@ async def _set_context(request: Request): if lei and UserActionContext.INSTITUTION in requirements: context = context | {UserActionContext.INSTITUTION: await get_institution_data(FiRequest(request, lei))} if period and UserActionContext.PERIOD in requirements: - context = context | { - UserActionContext.PERIOD: await repo.get_filing_period(request.state.db_session, period) - } + context = context | {UserActionContext.PERIOD: repo.get_filing_period(request.state.db_session, period)} if period and UserActionContext.FILING in requirements: - context = context | {UserActionContext.FILING: await repo.get_filing(request.state.db_session, lei, period)} + context = context | {UserActionContext.FILING: repo.get_filing(request.state.db_session, lei, period)} request.state.context = context return _set_context diff --git a/src/sbl_filing_api/services/submission_processor.py b/src/sbl_filing_api/services/submission_processor.py index bc3d983f..fb462a79 100644 --- a/src/sbl_filing_api/services/submission_processor.py +++ b/src/sbl_filing_api/services/submission_processor.py @@ -65,15 +65,15 @@ def generate_file_path(period_code: str, lei: str, file_identifier: str, extensi return file_path -async def validate_and_update_submission( +def validate_and_update_submission( period_code: str, lei: str, submission: SubmissionDAO, content: bytes, exec_check: dict ): - async with SessionLocal() as session: + with SessionLocal() as session: try: validator_version = imeta.version("regtech-data-validator") submission.validation_ruleset_version = validator_version submission.state = SubmissionState.VALIDATION_IN_PROGRESS - submission = await update_submission(session, submission) + submission = update_submission(session, submission) file_path = generate_file_path(period_code, lei, submission.counter) @@ -118,17 +118,17 @@ async def validate_and_update_submission( log.warning(f"Submission {submission.id} is expired, will not be updating final state with results.") return - await update_submission(session, submission) + update_submission(session, submission) except RuntimeError: log.exception("The file is malformed.") submission.state = SubmissionState.SUBMISSION_UPLOAD_MALFORMED - await update_submission(session, submission) + update_submission(session, submission) except Exception: log.exception("Validation for submission %d did not complete due to an unexpected error.", submission.id) submission.state = SubmissionState.VALIDATION_ERROR - await update_submission(session, submission) + update_submission(session, submission) def build_validation_results(final_df: pl.DataFrame, results: list[ValidationResults], final_phase: ValidationPhase): diff --git a/src/sbl_filing_api/services/validators/submission_validators.py b/src/sbl_filing_api/services/validators/submission_validators.py index c0ed055a..a7310e11 100644 --- a/src/sbl_filing_api/services/validators/submission_validators.py +++ b/src/sbl_filing_api/services/validators/submission_validators.py @@ -13,10 +13,8 @@ class ValidSubAccepted(ActionValidator): def __init__(self): super().__init__("valid_sub_accepted") - async def __call__(self, filing: FilingDAO, **kwargs): + def __call__(self, filing: FilingDAO, **kwargs): if filing: - submissions: List[SubmissionDAO] = await filing.awaitable_attrs.submissions + submissions: List[SubmissionDAO] = filing.submissions if not len(submissions) or submissions[0].state != SubmissionState.SUBMISSION_ACCEPTED: - filing.lei - filing.filing_period return f"Cannot sign filing. Filing for {filing.lei} for period {filing.filing_period} does not have a latest submission in the SUBMISSION_ACCEPTED state." diff --git a/tests/api/routers/test_filing_api.py b/tests/api/routers/test_filing_api.py index 4f298b19..ae26d79c 100644 --- a/tests/api/routers/test_filing_api.py +++ b/tests/api/routers/test_filing_api.py @@ -6,7 +6,7 @@ from copy import deepcopy from datetime import datetime as dt -from unittest.mock import ANY, Mock, AsyncMock +from unittest.mock import ANY, Mock from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient @@ -327,12 +327,11 @@ def test_authed_upload_file( mock_get_loop.return_value = mock_event_loop mock_event_loop.run_in_executor.return_value = asyncio.Future() - async_mock = AsyncMock(return_value=return_sub) mock_add_submission = mocker.patch( - "sbl_filing_api.entities.repos.submission_repo.add_submission", side_effect=async_mock + "sbl_filing_api.entities.repos.submission_repo.add_submission", return_value=return_sub ) mock_update_submission = mocker.patch( - "sbl_filing_api.entities.repos.submission_repo.update_submission", side_effect=async_mock + "sbl_filing_api.entities.repos.submission_repo.update_submission", return_value=return_sub ) mock_add_submitter = mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_user_action") mock_add_submitter.return_value = user_action_submit @@ -409,14 +408,13 @@ def test_submission_update_fail( mock_validate_file = mocker.patch("sbl_filing_api.services.submission_processor.validate_file_processable") mock_validate_file.return_value = None - async_mock = AsyncMock(return_value=return_sub) - mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_submission", side_effect=async_mock) + mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_submission", return_value=return_sub) mock_upload = mocker.patch("sbl_filing_api.services.submission_processor.upload_to_storage") mock_upload.return_value = None mock_update_submission = mocker.patch( - "sbl_filing_api.entities.repos.submission_repo.update_submission", side_effect=async_mock + "sbl_filing_api.entities.repos.submission_repo.update_submission", return_value=return_sub ) mock_add_submitter = mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_user_action") @@ -468,8 +466,7 @@ def test_submission_second_update_fail( mock_validate_file = mocker.patch("sbl_filing_api.services.submission_processor.validate_file_processable") mock_validate_file.return_value = None - async_mock = AsyncMock(return_value=return_sub) - mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_submission", side_effect=async_mock) + mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_submission", return_value=return_sub) mock_upload = mocker.patch("sbl_filing_api.services.submission_processor.upload_to_storage") mock_upload.return_value = None @@ -479,8 +476,8 @@ def test_submission_second_update_fail( side_effect=Exception("Can't connect to database"), ) - mock_add_submitter = mocker.patch("sbl_filing_api.entities.repos.submission_repo.add_user_action") - mock_add_submitter.side_effect = AsyncMock( + mocker.patch( + "sbl_filing_api.entities.repos.submission_repo.add_user_action", return_value=UserActionDAO( id=2, user_id="123456-7890-ABCDEF-GHIJ", @@ -488,7 +485,7 @@ def test_submission_second_update_fail( user_email="test@local.host", action_type=UserActionType.SUBMIT, timestamp=datetime.datetime.now(), - ) + ), ) file = {"file": ("submission.csv", open(submission_csv, "rb"))} diff --git a/tests/app/test_config.py b/tests/app/test_config.py index 1f013825..12abf673 100644 --- a/tests/app/test_config.py +++ b/tests/app/test_config.py @@ -10,7 +10,7 @@ def test_postgres_dsn_building(): "db_scehma": "test", } settings = Settings(**mock_config) - assert str(settings.conn) == "postgresql+asyncpg://user:%5Cz9-%2Ftgb76%23%40@test:5432/test" + assert str(settings.conn) == "postgresql+psycopg2://user:%5Cz9-%2Ftgb76%23%40@test:5432/test" def test_default_maxes(): diff --git a/tests/entities/conftest.py b/tests/entities/conftest.py index af935639..e6377af0 100644 --- a/tests/entities/conftest.py +++ b/tests/entities/conftest.py @@ -1,13 +1,9 @@ import asyncio import pytest -from asyncio import current_task -from sqlalchemy.ext.asyncio import ( - create_async_engine, - AsyncEngine, - async_scoped_session, - async_sessionmaker, -) +from sqlalchemy import Engine, create_engine +from sqlalchemy.orm import scoped_session, sessionmaker + from unittest.mock import Mock from sbl_filing_api.entities.models.dao import Base from regtech_api_commons.models.auth import AuthenticatedUser @@ -24,43 +20,37 @@ def event_loop(): @pytest.fixture(scope="session") def engine(): - return create_async_engine("sqlite+aiosqlite://") + return create_engine("sqlite://") @pytest.fixture(scope="function", autouse=True) -async def setup_db( +def setup_db( request: pytest.FixtureRequest, - engine: AsyncEngine, - event_loop: asyncio.AbstractEventLoop, + engine: Engine, ): - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) + Base.metadata.create_all(engine) def teardown(): - async def td(): - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - - event_loop.run_until_complete(td()) + Base.metadata.drop_all(engine) request.addfinalizer(teardown) @pytest.fixture(scope="function") -async def transaction_session(session_generator: async_scoped_session): - async with session_generator() as session: +def transaction_session(session_generator: scoped_session): + with session_generator() as session: yield session @pytest.fixture(scope="function") -async def query_session(session_generator: async_scoped_session): - async with session_generator() as session: +def query_session(session_generator: scoped_session): + with session_generator() as session: yield session @pytest.fixture(scope="function") -def session_generator(engine: AsyncEngine): - return async_scoped_session(async_sessionmaker(engine, expire_on_commit=False), current_task) +def session_generator(engine: Engine): + return scoped_session(sessionmaker(engine, expire_on_commit=False)) @pytest.fixture diff --git a/tests/entities/repos/test_submission_repo.py b/tests/entities/repos/test_submission_repo.py index ab9bb6b8..d9a79c5f 100644 --- a/tests/entities/repos/test_submission_repo.py +++ b/tests/entities/repos/test_submission_repo.py @@ -6,7 +6,7 @@ from datetime import datetime as dt from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session +from sqlalchemy.orm import Session, scoped_session from sbl_filing_api.entities.models.dao import ( SubmissionDAO, @@ -27,9 +27,7 @@ class TestSubmissionRepo: @pytest.fixture(scope="function", autouse=True) - async def setup( - self, transaction_session: AsyncSession, mocker: MockerFixture, session_generator: async_scoped_session - ): + def setup(self, transaction_session: Session, mocker: MockerFixture, session_generator: scoped_session): mocker.patch.object(repo, "SessionLocal", return_value=session_generator()) user_action1 = UserActionDAO( @@ -214,9 +212,9 @@ async def setup( transaction_session.add(contact_info1) transaction_session.add(contact_info2) - await transaction_session.commit() + transaction_session.commit() - async def test_add_filing_period(self, transaction_session: AsyncSession): + def test_add_filing_period(self, transaction_session: Session): new_fp = FilingPeriodDTO( code="2024Q1", description="Filing Period 2024 Q1", @@ -225,23 +223,23 @@ async def test_add_filing_period(self, transaction_session: AsyncSession): due=dt.now(), filing_type=FilingType.ANNUAL, ) - res = await repo.upsert_filing_period(transaction_session, new_fp) + res = repo.upsert_filing_period(transaction_session, new_fp) assert res.code == "2024Q1" assert res.description == "Filing Period 2024 Q1" - async def test_get_filing_periods(self, query_session: AsyncSession): - res = await repo.get_filing_periods(query_session) + def test_get_filing_periods(self, query_session: Session): + res = repo.get_filing_periods(query_session) assert len(res) == 1 assert res[0].code == "2024" assert res[0].description == "Filing Period 2024" - async def test_get_filing_period(self, query_session: AsyncSession): - res = await repo.get_filing_period(query_session, filing_period="2024") + def test_get_filing_period(self, query_session: Session): + res = repo.get_filing_period(query_session, filing_period="2024") assert res.code == "2024" assert res.filing_type == FilingType.ANNUAL - async def test_add_filing(self, transaction_session: AsyncSession): - user_action_create = await repo.add_user_action( + def test_add_filing(self, transaction_session: Session): + user_action_create = repo.add_user_action( transaction_session, UserActionDTO( user_id="123456-7890-ABCDEF-GHIJ", @@ -250,7 +248,7 @@ async def test_add_filing(self, transaction_session: AsyncSession): action_type=UserActionType.CREATE, ), ) - res = await repo.create_new_filing( + res = repo.create_new_filing( transaction_session, lei="12345ABCDE", filing_period="2024", creator_id=user_action_create.id ) assert res.id == 4 @@ -263,8 +261,8 @@ async def test_add_filing(self, transaction_session: AsyncSession): assert res.creator.user_email == "test@local.host" assert res.creator.action_type == UserActionType.CREATE - async def test_modify_filing(self, transaction_session: AsyncSession): - user_action_create = await repo.add_user_action( + def test_modify_filing(self, transaction_session: Session): + user_action_create = repo.add_user_action( transaction_session, UserActionDTO( user_id="123456-7890-ABCDEF-GHIJ", @@ -283,7 +281,7 @@ async def test_modify_filing(self, transaction_session: AsyncSession): creator_id=user_action_create.id, ) - res = await repo.upsert_filing(transaction_session, mod_filing) + res = repo.upsert_filing(transaction_session, mod_filing) assert res.id == 3 assert res.filing_period == "2024" assert res.lei == "ZYXWVUTSRQP" @@ -292,8 +290,8 @@ async def test_modify_filing(self, transaction_session: AsyncSession): assert res.creator.user_id == "123456-7890-ABCDEF-GHIJ" assert res.creator.user_name == "test creator" - async def test_get_filing(self, query_session: AsyncSession, mocker: MockerFixture): - res1 = await repo.get_filing(query_session, lei="1234567890", filing_period="2024") + def test_get_filing(self, query_session: Session, mocker: MockerFixture): + res1 = repo.get_filing(query_session, lei="1234567890", filing_period="2024") assert res1.id == 1 assert res1.filing_period == "2024" assert res1.lei == "1234567890" @@ -301,13 +299,13 @@ async def test_get_filing(self, query_session: AsyncSession, mocker: MockerFixtu assert res1.signatures[0].id == 5 assert res1.signatures[0].user_id == "test_sig@local.host" - res2 = await repo.get_filing(query_session, lei="ABCDEFGHIJ", filing_period="2024") + res2 = repo.get_filing(query_session, lei="ABCDEFGHIJ", filing_period="2024") assert res2.id == 2 assert res2.filing_period == "2024" assert res2.lei == "ABCDEFGHIJ" - async def test_get_filings(self, query_session: AsyncSession, mocker: MockerFixture): - res = await repo.get_filings(query_session, leis=["1234567890", "ABCDEFGHIJ"], filing_period="2024") + def test_get_filings(self, query_session: Session, mocker: MockerFixture): + res = repo.get_filings(query_session, leis=["1234567890", "ABCDEFGHIJ"], filing_period="2024") assert res[0].id == 1 assert res[0].filing_period == "2024" assert res[0].lei == "1234567890" @@ -316,8 +314,8 @@ async def test_get_filings(self, query_session: AsyncSession, mocker: MockerFixt assert res[1].filing_period == "2024" assert res[1].lei == "ABCDEFGHIJ" - async def test_get_period_filings(self, query_session: AsyncSession, mocker: MockerFixture): - results = await repo.get_period_filings(query_session, filing_period="2024") + def test_get_period_filings(self, query_session: Session, mocker: MockerFixture): + results = repo.get_period_filings(query_session, filing_period="2024") assert len(results) == 3 assert results[0].id == 1 assert results[0].lei == "1234567890" @@ -329,47 +327,47 @@ async def test_get_period_filings(self, query_session: AsyncSession, mocker: Moc assert results[2].lei == "ZYXWVUTSRQP" assert results[2].filing_period == "2024" - async def test_get_latest_submission(self, query_session: AsyncSession): - res = await repo.get_latest_submission(query_session, lei="ABCDEFGHIJ", filing_period="2024") + def test_get_latest_submission(self, query_session: Session): + res = repo.get_latest_submission(query_session, lei="ABCDEFGHIJ", filing_period="2024") assert res.id == 3 assert res.filing == 2 assert res.state == SubmissionState.SUBMISSION_UPLOADED assert res.validation_ruleset_version == "v1" - async def test_get_submission(self, query_session: AsyncSession): - res = await repo.get_submission(query_session, 1) + def test_get_submission(self, query_session: Session): + res = repo.get_submission(query_session, 1) assert res.id == 1 assert res.filing == 1 assert res.state == SubmissionState.SUBMISSION_UPLOADED assert res.validation_ruleset_version == "v1" - async def test_get_submission_by_counter(self, query_session: AsyncSession): - res = await repo.get_submission_by_counter(query_session, "ABCDEFGHIJ", "2024", 2) + def test_get_submission_by_counter(self, query_session: Session): + res = repo.get_submission_by_counter(query_session, "ABCDEFGHIJ", "2024", 2) assert res.id == 3 assert res.filing == 2 assert res.state == SubmissionState.SUBMISSION_UPLOADED assert res.validation_ruleset_version == "v1" assert res.filename == "file3.csv" - async def test_get_submissions(self, query_session: AsyncSession): - res = await repo.get_submissions(query_session) + def test_get_submissions(self, query_session: Session): + res = repo.get_submissions(query_session) assert len(res) == 4 assert {1, 2, 3, 4} == set([s.id for s in res]) assert res[1].filing == 2 assert res[2].state == SubmissionState.SUBMISSION_UPLOADED - res = await repo.get_submissions(query_session, lei="ABCDEFGHIJ", filing_period="2024") + res = repo.get_submissions(query_session, lei="ABCDEFGHIJ", filing_period="2024") assert len(res) == 2 assert {2, 3} == set([s.id for s in res]) assert {2} == set([s.filing for s in res]) assert {SubmissionState.SUBMISSION_UPLOADED} == set([s.state for s in res]) # verify a filing with no submissions behaves ok - res = await repo.get_submissions(query_session, lei="ZYXWVUTSRQP", filing_period="2024") + res = repo.get_submissions(query_session, lei="ZYXWVUTSRQP", filing_period="2024") assert len(res) == 0 - async def test_add_submission(self, transaction_session: AsyncSession): - user_action_submit = await repo.add_user_action( + def test_add_submission(self, transaction_session: Session): + user_action_submit = repo.add_user_action( transaction_session, UserActionDTO( user_id="123456-7890-ABCDEF-GHIJ", @@ -379,7 +377,7 @@ async def test_add_submission(self, transaction_session: AsyncSession): ), ) - res = await repo.add_submission( + res = repo.add_submission( transaction_session, filing_id=1, filename="file1.csv", submitter_id=user_action_submit.id ) assert res.id == 5 @@ -392,13 +390,13 @@ async def test_add_submission(self, transaction_session: AsyncSession): assert res.submitter.user_email == user_action_submit.user_email assert res.submitter.action_type == UserActionType.SUBMIT - async def test_error_out_submission(self, transaction_session: AsyncSession): - await repo.error_out_submission(4) - expired_sub = await repo.get_submission(transaction_session, 4) + def test_error_out_submission(self, transaction_session: Session): + repo.error_out_submission(4) + expired_sub = repo.get_submission(transaction_session, 4) assert expired_sub.id == 4 assert expired_sub.state == SubmissionState.VALIDATION_ERROR - async def test_update_submission(self, session_generator: async_scoped_session): + def test_update_submission(self, session_generator: scoped_session): user_action_submit = UserActionDAO( id=2, user_id="123456-7890-ABCDEF-GHIJ", @@ -407,46 +405,46 @@ async def test_update_submission(self, session_generator: async_scoped_session): action_type=UserActionType.SUBMIT, timestamp=datetime.datetime.now(), ) - async with session_generator() as add_session: - res = await repo.add_submission( + with session_generator() as add_session: + res = repo.add_submission( add_session, filing_id=1, filename="file1.csv", submitter_id=user_action_submit.id ) - async with session_generator() as update_session: + with session_generator() as update_session: res.state = SubmissionState.VALIDATION_IN_PROGRESS - res = await repo.update_submission(update_session, res) + res = repo.update_submission(update_session, res) - async def query_updated_dao(): - async with session_generator() as search_session: + def query_updated_dao(): + with session_generator() as search_session: stmt = select(SubmissionDAO).filter(SubmissionDAO.id == 5) - new_res1 = await search_session.scalar(stmt) + new_res1 = search_session.scalar(stmt) assert new_res1.id == 5 assert new_res1.filing == 1 assert new_res1.state == SubmissionState.VALIDATION_IN_PROGRESS - await query_updated_dao() + query_updated_dao() validation_results = self.get_error_json() res.validation_results = validation_results res.state = SubmissionState.VALIDATION_WITH_ERRORS # to test passing in a session to the update_submission function - async with session_generator() as update_session: - res = await repo.update_submission(update_session, res) + with session_generator() as update_session: + res = repo.update_submission(update_session, res) - async def query_updated_dao(): - async with session_generator() as search_session: + def query_updated_dao(): + with session_generator() as search_session: stmt = select(SubmissionDAO).filter(SubmissionDAO.id == 5) - new_res2 = await search_session.scalar(stmt) + new_res2 = search_session.scalar(stmt) assert new_res2.id == 5 assert new_res2.filing == 1 assert new_res2.counter == 3 assert new_res2.state == SubmissionState.VALIDATION_WITH_ERRORS assert new_res2.validation_results == validation_results - await query_updated_dao() + query_updated_dao() - async def test_get_contact_info(self, query_session: AsyncSession): - res = await repo.get_filing(session=query_session, lei="ABCDEFGHIJ", filing_period="2024") + def test_get_contact_info(self, query_session: Session): + res = repo.get_filing(session=query_session, lei="ABCDEFGHIJ", filing_period="2024") assert res.contact_info.id == 2 assert res.contact_info.filing == 2 @@ -463,8 +461,8 @@ async def test_get_contact_info(self, query_session: AsyncSession): assert res.contact_info.phone_ext == "x54321" assert res.contact_info.email == "test2@cfpb.gov" - async def test_create_contact_info(self, transaction_session: AsyncSession): - filing = await repo.update_contact_info( + def test_create_contact_info(self, transaction_session: Session): + filing = repo.update_contact_info( transaction_session, lei="ZYXWVUTSRQP", filing_period="2024", @@ -498,14 +496,14 @@ async def test_create_contact_info(self, transaction_session: AsyncSession): assert filing.contact_info.phone_number == "312-345-6789" assert filing.contact_info.email == "test3@cfpb.gov" - async def test_create_contact_info_invalid_field_length(self, transaction_session: AsyncSession): + def test_create_contact_info_invalid_field_length(self, transaction_session: Session): out_of_range_text = ( "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget " "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, " "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis..." ) with pytest.raises(Exception) as e: - await repo.update_contact_info( + repo.update_contact_info( transaction_session, lei="ZYXWVUTSRQP", filing_period="2024", @@ -526,8 +524,8 @@ async def test_create_contact_info_invalid_field_length(self, transaction_sessio ) assert isinstance(e.value, ValidationError) - async def test_update_contact_info(self, transaction_session: AsyncSession): - filing = await repo.update_contact_info( + def test_update_contact_info(self, transaction_session: Session): + filing = repo.update_contact_info( transaction_session, lei="ABCDEFGHIJ", filing_period="2024", @@ -565,14 +563,14 @@ async def test_update_contact_info(self, transaction_session: AsyncSession): assert filing.contact_info.phone_ext == "x12345" assert filing.contact_info.email == "test2_upd@cfpb.gov" - async def test_update_contact_info_invalid_field_length(self, transaction_session: AsyncSession): + def test_update_contact_info_invalid_field_length(self, transaction_session: Session): out_of_range_text = ( "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget " "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, " "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis..." ) with pytest.raises(Exception) as e: - await repo.update_contact_info( + repo.update_contact_info( transaction_session, lei="ABCDEFGHIJ", filing_period="2024", @@ -595,25 +593,25 @@ async def test_update_contact_info_invalid_field_length(self, transaction_sessio ) assert isinstance(e.value, ValidationError) - async def test_get_user_action(self, query_session: AsyncSession): - res = await repo.get_user_action(session=query_session, id=3) + def test_get_user_action(self, query_session: Session): + res = repo.get_user_action(session=query_session, id=3) assert res.user_id == "test@local.host" assert res.user_name == "accepter name" assert res.user_email == "test@local.host" assert res.action_type == UserActionType.ACCEPT - async def test_get_user_actions(self, query_session: AsyncSession): - res = await repo.get_user_actions(session=query_session) + def test_get_user_actions(self, query_session: Session): + res = repo.get_user_actions(session=query_session) assert len(res) == 5 assert res[0].id == 1 assert res[0].user_name == "signer name" - async def test_add_user_action(self, query_session: AsyncSession, transaction_session: AsyncSession): - user_actions_in_repo = await repo.get_user_actions(query_session) + def test_add_user_action(self, query_session: Session, transaction_session: Session): + user_actions_in_repo = repo.get_user_actions(query_session) - accepter = await repo.add_user_action( + accepter = repo.add_user_action( transaction_session, UserActionDTO( user_id="test2@cfpb.gov", @@ -629,7 +627,7 @@ async def test_add_user_action(self, query_session: AsyncSession, transaction_se assert accepter.user_email == "test2@cfpb.gov" assert accepter.action_type == UserActionType.ACCEPT - async def test_add_user_action_invalid_field_length(self, transaction_session: AsyncSession): + def test_add_user_action_invalid_field_length(self, transaction_session: Session): out_of_range_text = ( "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget " "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, " @@ -639,7 +637,7 @@ async def test_add_user_action_invalid_field_length(self, transaction_session: A out_of_range_user_id = "123456789123456789123456789123456789123456789" with pytest.raises(ValidationError) as ve: - await repo.add_user_action( + repo.add_user_action( transaction_session, UserActionDTO( id=1, diff --git a/tests/services/test_submission_processor.py b/tests/services/test_submission_processor.py index 6dc85237..5e0b662c 100644 --- a/tests/services/test_submission_processor.py +++ b/tests/services/test_submission_processor.py @@ -19,17 +19,17 @@ def mock_upload_file(self, mocker: MockerFixture) -> Mock: file_mock = mocker.patch("fastapi.UploadFile") return file_mock.return_value - async def test_upload(self, mocker: MockerFixture): + def test_upload(self, mocker: MockerFixture): upload_mock = mocker.patch("sbl_filing_api.services.file_handler.upload") submission_processor.upload_to_storage("test_period", "test", "test", b"test content local") upload_mock.assert_called_once_with(path="upload/test_period/test/test.csv", content=b"test content local") - async def test_read_from_storage(self, mocker: MockerFixture): + def test_read_from_storage(self, mocker: MockerFixture): download_mock = mocker.patch("sbl_filing_api.services.file_handler.download") submission_processor.get_from_storage("2024", "1234567890", "1_report") download_mock.assert_called_with("upload/2024/1234567890/1_report.csv") - async def test_upload_failure(self, mocker: MockerFixture): + def test_upload_failure(self, mocker: MockerFixture): upload_mock = mocker.patch("sbl_filing_api.services.file_handler.upload") upload_mock.side_effect = IOError("test") with pytest.raises(Exception) as e: @@ -37,7 +37,7 @@ async def test_upload_failure(self, mocker: MockerFixture): assert isinstance(e.value, RegTechHttpException) assert e.value.name == "Upload Failure" - async def test_read_failure(self, mocker: MockerFixture): + def test_read_failure(self, mocker: MockerFixture): download_mock = mocker.patch("sbl_filing_api.services.file_handler.download") download_mock.side_effect = IOError("test") with pytest.raises(Exception) as e: @@ -75,7 +75,7 @@ def test_file_not_supported_file_size_too_large(self, mock_upload_file: Mock): submission_processor.validate_file_processable(mock_upload_file) assert e.value.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE - async def test_validate_and_update_successful( + def test_validate_and_update_successful( self, mocker: MockerFixture, successful_submission_mock: Mock, @@ -95,9 +95,7 @@ async def test_validate_and_update_successful( file_mock = mocker.patch("sbl_filing_api.services.submission_processor.upload_to_storage") - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) file_mock.assert_called_once_with( "2024", @@ -109,7 +107,7 @@ async def test_validate_and_update_successful( assert successful_submission_mock.mock_calls[0].args[1].validation_ruleset_version == "0.1.0" assert successful_submission_mock.mock_calls[1].args[1].state == "VALIDATION_SUCCESSFUL" - async def test_validate_and_update_warnings( + def test_validate_and_update_warnings( self, mocker: MockerFixture, warning_submission_mock: Mock, @@ -131,9 +129,7 @@ async def test_validate_and_update_warnings( file_mock = mocker.patch("sbl_filing_api.services.submission_processor.upload_to_storage") - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) file_mock.assert_called_once_with( "2024", @@ -145,7 +141,7 @@ async def test_validate_and_update_warnings( assert warning_submission_mock.mock_calls[0].args[1].validation_ruleset_version == "0.1.0" assert warning_submission_mock.mock_calls[1].args[1].state == SubmissionState.VALIDATION_WITH_WARNINGS - async def test_validate_and_update_errors( + def test_validate_and_update_errors( self, mocker: MockerFixture, error_submission_mock: Mock, @@ -167,9 +163,7 @@ async def test_validate_and_update_errors( file_mock = mocker.patch("sbl_filing_api.services.submission_processor.upload_to_storage") - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) file_mock.assert_called_once_with( "2024", @@ -181,7 +175,7 @@ async def test_validate_and_update_errors( assert error_submission_mock.mock_calls[0].args[1].validation_ruleset_version == "0.1.0" assert error_submission_mock.mock_calls[1].args[1].state == SubmissionState.VALIDATION_WITH_ERRORS - async def test_validate_and_update_submission_malformed( + def test_validate_and_update_submission_malformed( self, mocker: MockerFixture, ): @@ -206,9 +200,7 @@ async def test_validate_and_update_submission_malformed( re = RuntimeError("File not in csv format") mock_read_csv.side_effect = re - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) mock_update_submission.assert_called() log_mock.exception.assert_called_with("The file is malformed.") @@ -221,9 +213,7 @@ async def test_validate_and_update_submission_malformed( re = RuntimeError("File can not be parsed by validator") mock_validation.side_effect = re - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) log_mock.exception.assert_called_with("The file is malformed.") assert mock_update_submission.mock_calls[0].args[1].state == SubmissionState.VALIDATION_IN_PROGRESS assert mock_update_submission.mock_calls[1].args[1].state == SubmissionState.SUBMISSION_UPLOAD_MALFORMED @@ -231,14 +221,12 @@ async def test_validate_and_update_submission_malformed( e = Exception("Test exception") mock_validation.side_effect = e - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": True} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": True}) log_mock.exception.assert_called_with( "Validation for submission %d did not complete due to an unexpected error.", mock_sub.id ) - async def test_validation_expired( + def test_validation_expired( self, mocker: MockerFixture, validate_submission_mock: Mock, @@ -266,15 +254,13 @@ async def test_validation_expired( mock_build_json = mocker.patch("sbl_filing_api.services.submission_processor.build_validation_results") mock_build_json.return_value = {"logic_errors": {"total_count": 1}} - await submission_processor.validate_and_update_submission( - "2024", "123456790", mock_sub, None, {"continue": False} - ) + submission_processor.validate_and_update_submission("2024", "123456790", mock_sub, None, {"continue": False}) # second update shouldn't be called assert len(mock_update_submission.mock_calls) == 1 log_mock.warning.assert_called_with("Submission 1 is expired, will not be updating final state with results.") - async def test_build_validation_results_success(self, mocker: MockerFixture): + def test_build_validation_results_success(self, mocker: MockerFixture): df_to_dicts_mock = mocker.patch("sbl_filing_api.services.submission_processor.df_to_dicts") df_to_dicts_mock.return_value = [] @@ -290,7 +276,7 @@ async def test_build_validation_results_success(self, mocker: MockerFixture): assert validation_results["logic_warnings"]["multi_field_count"] == 0 assert validation_results["logic_warnings"]["register_count"] == 0 - async def test_build_validation_results_syntax_errors(self, mocker: MockerFixture): + def test_build_validation_results_syntax_errors(self, mocker: MockerFixture): df_to_dicts_mock = mocker.patch("sbl_filing_api.services.submission_processor.df_to_dicts") df_to_dicts_mock.return_value = [