From 52bf45dfcdc213de0e0e4374d1db050b7b440c89 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:38:03 +0300 Subject: [PATCH 01/68] Add config for Gunicorn --- gunicorn.conf.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 gunicorn.conf.py diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000..9f32890 --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,12 @@ +import os + +host = os.getenv("APP_HOST", "app") +port = os.getenv("APP_PORT", "8000") +bind = f"{host}:{port}" +worker_class = "uvicorn.workers.UvicornWorker" +forwarded_allow_ips = "*" + +# Production settings +if os.getenv("DEBUG") == "0": + loglevel = "warning" + workers = int(os.getenv("GUNICORN_WORKERS", "2")) From 4aabf66ac584dab34a2d3f4ed6f91813ba348fd8 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:38:23 +0300 Subject: [PATCH 02/68] Add script to run the application --- run.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 run.sh diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..0f7dd7d --- /dev/null +++ b/run.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Exit on errors +set -e + +echo "Running database migrations ..." +alembic -c alembic/alembic.ini upgrade head + +if [ "$DEBUG" = "1" ]; then + echo "Running application in debug mode ..." + uvicorn app.main.app:app \ + --host ${APP_HOST:-app} \ + --port ${APP_PORT:-8000} \ + --reload +else + echo "Running application in production mode ..." + gunicorn app.main.app:app -c gunicorn.conf.py +fi From 6b84347aae5146d6b780ec886871250755608ee9 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:38:39 +0300 Subject: [PATCH 03/68] Locked uv packages --- uv.lock | 1036 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1036 insertions(+) create mode 100644 uv.lock diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..349caa5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1036 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "alembic" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/52/72e791b75c6b1efa803e491f7cbab78e963695e76d4ada05385252927e76/alembic-1.16.4.tar.gz", hash = "sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2", size = 1968161, upload-time = "2025-07-10T16:17:20.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/62/96b5217b742805236614f05904541000f55422a6060a90d7fd4ce26c172d/alembic-1.16.4-py3-none-any.whl", hash = "sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d", size = 247026, upload-time = "2025-07-10T16:17:21.845Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162, upload-time = "2024-10-20T00:29:41.88Z" }, + { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025, upload-time = "2024-10-20T00:29:43.352Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243, upload-time = "2024-10-20T00:29:44.922Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059, upload-time = "2024-10-20T00:29:46.891Z" }, + { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596, upload-time = "2024-10-20T00:29:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632, upload-time = "2024-10-20T00:29:50.768Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186, upload-time = "2024-10-20T00:29:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064, upload-time = "2024-10-20T00:29:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" }, +] + +[[package]] +name = "authapigateway" +version = "0.0.1" +source = { virtual = "." } +dependencies = [ + { name = "alembic" }, + { name = "asyncpg" }, + { name = "bcrypt" }, + { name = "email-validator" }, + { name = "fastapi-slim" }, + { name = "gunicorn" }, + { name = "itsdangerous" }, + { name = "passlib" }, + { name = "pydantic-settings" }, + { name = "python-jose" }, + { name = "python-multipart" }, + { name = "redis" }, + { name = "sqladmin" }, + { name = "sqlmodel" }, + { name = "typer" }, + { name = "uvicorn" }, +] + +[package.dev-dependencies] +test = [ + { name = "aiosqlite" }, + { name = "coverage" }, + { name = "faker" }, + { name = "fakeredis" }, + { name = "httpx" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "types-passlib" }, + { name = "types-python-jose" }, + { name = "types-wtforms" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic", specifier = ">=1.16.4" }, + { name = "asyncpg", specifier = ">=0.30.0" }, + { name = "bcrypt", specifier = "==4.0.1" }, + { name = "email-validator", specifier = ">=2.3.0" }, + { name = "fastapi-slim", specifier = ">=0.116.1" }, + { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "itsdangerous", specifier = ">=2.2.0" }, + { name = "passlib", specifier = ">=1.7.4" }, + { name = "pydantic-settings", specifier = ">=2.10.1" }, + { name = "python-jose", specifier = ">=3.5.0" }, + { name = "python-multipart", specifier = ">=0.0.20" }, + { name = "redis", specifier = ">=6.4.0" }, + { name = "sqladmin", specifier = ">=0.21.0" }, + { name = "sqlmodel", specifier = ">=0.0.24" }, + { name = "typer", specifier = ">=0.16.1" }, + { name = "uvicorn", specifier = ">=0.35.0" }, +] + +[package.metadata.requires-dev] +test = [ + { name = "aiosqlite", specifier = ">=0.21.0" }, + { name = "coverage", specifier = ">=7.10.5" }, + { name = "faker", specifier = ">=37.6.0" }, + { name = "fakeredis", specifier = ">=2.31.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mypy", specifier = ">=1.17.1" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-cov", specifier = ">=6.2.1" }, + { name = "ruff", specifier = ">=0.12.10" }, + { name = "types-passlib", specifier = ">=1.7.7.20250602" }, + { name = "types-python-jose", specifier = ">=3.5.0.20250531" }, + { name = "types-wtforms", specifier = ">=3.2.1.20250809" }, +] + +[[package]] +name = "bcrypt" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/ae/3af7d006aacf513975fd1948a6b4d6f8b4a307f8a244e1a3d3774b297aad/bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd", size = 25498, upload-time = "2022-10-09T15:36:49.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/d4/3b2657bd58ef02b23a07729b0df26f21af97169dbd0b5797afa9e97ebb49/bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f", size = 473446, upload-time = "2022-10-09T15:36:25.481Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0a/1582790232fef6c2aa201f345577306b8bfe465c2c665dec04c86a016879/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0", size = 583044, upload-time = "2022-10-09T15:37:09.447Z" }, + { url = "https://files.pythonhosted.org/packages/41/16/49ff5146fb815742ad58cafb5034907aa7f166b1344d0ddd7fd1c818bd17/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410", size = 583189, upload-time = "2022-10-09T15:37:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/aa/48/fd2b197a9741fa790ba0b88a9b10b5e88e62ff5cf3e1bc96d8354d7ce613/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344", size = 593473, upload-time = "2022-10-09T15:36:27.195Z" }, + { url = "https://files.pythonhosted.org/packages/7d/50/e683d8418974a602ba40899c8a5c38b3decaf5a4d36c32fc65dce454d8a8/bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a", size = 593249, upload-time = "2022-10-09T15:36:28.481Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a7/ee4561fd9b78ca23c8e5591c150cc58626a5dfb169345ab18e1c2c664ee0/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3", size = 583586, upload-time = "2022-10-09T15:37:11.962Z" }, + { url = "https://files.pythonhosted.org/packages/64/fe/da28a5916128d541da0993328dc5cf4b43dfbf6655f2c7a2abe26ca2dc88/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2", size = 593659, upload-time = "2022-10-09T15:36:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4f/3632a69ce344c1551f7c9803196b191a8181c6a1ad2362c225581ef0d383/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535", size = 613116, upload-time = "2022-10-09T15:37:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/87/69/edacb37481d360d06fc947dab5734aaf511acb7d1a1f9e2849454376c0f8/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e", size = 624290, upload-time = "2022-10-09T15:36:31.251Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/6a534669890725cbb8c1fb4622019be31813c8edaa7b6d5b62fc9360a17e/bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab", size = 159428, upload-time = "2022-10-09T15:36:32.893Z" }, + { url = "https://files.pythonhosted.org/packages/46/81/d8c22cd7e5e1c6a7d48e41a1d1d46c92f17dae70a54d9814f746e6027dec/bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9", size = 152930, upload-time = "2022-10-09T15:36:34.635Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/83/153f54356c7c200013a752ce1ed5448573dca546ce125801afca9e1ac1a4/coverage-7.10.5.tar.gz", hash = "sha256:f2e57716a78bc3ae80b2207be0709a3b2b63b9f2dcf9740ee6ac03588a2015b6", size = 821662, upload-time = "2025-08-23T14:42:44.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/8e/40d75c7128f871ea0fd829d3e7e4a14460cad7c3826e3b472e6471ad05bd/coverage-7.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c2d05c7e73c60a4cecc7d9b60dbfd603b4ebc0adafaef371445b47d0f805c8a9", size = 217077, upload-time = "2025-08-23T14:40:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/18/a8/f333f4cf3fb5477a7f727b4d603a2eb5c3c5611c7fe01329c2e13b23b678/coverage-7.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32ddaa3b2c509778ed5373b177eb2bf5662405493baeff52278a0b4f9415188b", size = 217310, upload-time = "2025-08-23T14:41:00.628Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2c/fbecd8381e0a07d1547922be819b4543a901402f63930313a519b937c668/coverage-7.10.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd382410039fe062097aa0292ab6335a3f1e7af7bba2ef8d27dcda484918f20c", size = 248802, upload-time = "2025-08-23T14:41:02.012Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bc/1011da599b414fb6c9c0f34086736126f9ff71f841755786a6b87601b088/coverage-7.10.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7fa22800f3908df31cea6fb230f20ac49e343515d968cc3a42b30d5c3ebf9b5a", size = 251550, upload-time = "2025-08-23T14:41:03.438Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6f/b5c03c0c721c067d21bc697accc3642f3cef9f087dac429c918c37a37437/coverage-7.10.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f366a57ac81f5e12797136552f5b7502fa053c861a009b91b80ed51f2ce651c6", size = 252684, upload-time = "2025-08-23T14:41:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/d474bc300ebcb6a38a1047d5c465a227605d6473e49b4e0d793102312bc5/coverage-7.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1dc8f1980a272ad4a6c84cba7981792344dad33bf5869361576b7aef42733a", size = 250602, upload-time = "2025-08-23T14:41:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2d/548c8e04249cbba3aba6bd799efdd11eee3941b70253733f5d355d689559/coverage-7.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2285c04ee8676f7938b02b4936d9b9b672064daab3187c20f73a55f3d70e6b4a", size = 248724, upload-time = "2025-08-23T14:41:08.429Z" }, + { url = "https://files.pythonhosted.org/packages/e2/96/a7c3c0562266ac39dcad271d0eec8fc20ab576e3e2f64130a845ad2a557b/coverage-7.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2492e4dd9daab63f5f56286f8a04c51323d237631eb98505d87e4c4ff19ec34", size = 250158, upload-time = "2025-08-23T14:41:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/75/74d4be58c70c42ef0b352d597b022baf12dbe2b43e7cb1525f56a0fb1d4b/coverage-7.10.5-cp312-cp312-win32.whl", hash = "sha256:38a9109c4ee8135d5df5505384fc2f20287a47ccbe0b3f04c53c9a1989c2bbaf", size = 219493, upload-time = "2025-08-23T14:41:11.095Z" }, + { url = "https://files.pythonhosted.org/packages/4f/08/364e6012d1d4d09d1e27437382967efed971d7613f94bca9add25f0c1f2b/coverage-7.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:6b87f1ad60b30bc3c43c66afa7db6b22a3109902e28c5094957626a0143a001f", size = 220302, upload-time = "2025-08-23T14:41:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/7c8a365e1f7355c58af4fe5faf3f90cc8e587590f5854808d17ccb4e7077/coverage-7.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:672a6c1da5aea6c629819a0e1461e89d244f78d7b60c424ecf4f1f2556c041d8", size = 218936, upload-time = "2025-08-23T14:41:13.872Z" }, + { url = "https://files.pythonhosted.org/packages/9f/08/4166ecfb60ba011444f38a5a6107814b80c34c717bc7a23be0d22e92ca09/coverage-7.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef3b83594d933020f54cf65ea1f4405d1f4e41a009c46df629dd964fcb6e907c", size = 217106, upload-time = "2025-08-23T14:41:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/d7/b71022408adbf040a680b8c64bf6ead3be37b553e5844f7465643979f7ca/coverage-7.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b96bfdf7c0ea9faebce088a3ecb2382819da4fbc05c7b80040dbc428df6af44", size = 217353, upload-time = "2025-08-23T14:41:16.656Z" }, + { url = "https://files.pythonhosted.org/packages/74/68/21e0d254dbf8972bb8dd95e3fe7038f4be037ff04ba47d6d1b12b37510ba/coverage-7.10.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63df1fdaffa42d914d5c4d293e838937638bf75c794cf20bee12978fc8c4e3bc", size = 248350, upload-time = "2025-08-23T14:41:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/90/65/28752c3a896566ec93e0219fc4f47ff71bd2b745f51554c93e8dcb659796/coverage-7.10.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8002dc6a049aac0e81ecec97abfb08c01ef0c1fbf962d0c98da3950ace89b869", size = 250955, upload-time = "2025-08-23T14:41:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/ca6b7967f57f6fef31da8749ea20417790bb6723593c8cd98a987be20423/coverage-7.10.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63d4bb2966d6f5f705a6b0c6784c8969c468dbc4bcf9d9ded8bff1c7e092451f", size = 252230, upload-time = "2025-08-23T14:41:20.959Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/17a411b2a2a18f8b8c952aa01c00f9284a1fbc677c68a0003b772ea89104/coverage-7.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1f672efc0731a6846b157389b6e6d5d5e9e59d1d1a23a5c66a99fd58339914d5", size = 250387, upload-time = "2025-08-23T14:41:22.644Z" }, + { url = "https://files.pythonhosted.org/packages/c7/89/97a9e271188c2fbb3db82235c33980bcbc733da7da6065afbaa1d685a169/coverage-7.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3f39cef43d08049e8afc1fde4a5da8510fc6be843f8dea350ee46e2a26b2f54c", size = 248280, upload-time = "2025-08-23T14:41:24.061Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0ad7d0137257553eb4706b4ad6180bec0a1b6a648b092c5bbda48d0e5b2c/coverage-7.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2968647e3ed5a6c019a419264386b013979ff1fb67dd11f5c9886c43d6a31fc2", size = 249894, upload-time = "2025-08-23T14:41:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/56/fb3aba936addb4c9e5ea14f5979393f1c2466b4c89d10591fd05f2d6b2aa/coverage-7.10.5-cp313-cp313-win32.whl", hash = "sha256:0d511dda38595b2b6934c2b730a1fd57a3635c6aa2a04cb74714cdfdd53846f4", size = 219536, upload-time = "2025-08-23T14:41:27.694Z" }, + { url = "https://files.pythonhosted.org/packages/fc/54/baacb8f2f74431e3b175a9a2881feaa8feb6e2f187a0e7e3046f3c7742b2/coverage-7.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a86281794a393513cf117177fd39c796b3f8e3759bb2764259a2abba5cce54b", size = 220330, upload-time = "2025-08-23T14:41:29.081Z" }, + { url = "https://files.pythonhosted.org/packages/64/8a/82a3788f8e31dee51d350835b23d480548ea8621f3effd7c3ba3f7e5c006/coverage-7.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:cebd8e906eb98bb09c10d1feed16096700b1198d482267f8bf0474e63a7b8d84", size = 218961, upload-time = "2025-08-23T14:41:30.511Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a1/590154e6eae07beee3b111cc1f907c30da6fc8ce0a83ef756c72f3c7c748/coverage-7.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0520dff502da5e09d0d20781df74d8189ab334a1e40d5bafe2efaa4158e2d9e7", size = 217819, upload-time = "2025-08-23T14:41:31.962Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ff/436ffa3cfc7741f0973c5c89405307fe39b78dcf201565b934e6616fc4ad/coverage-7.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d9cd64aca68f503ed3f1f18c7c9174cbb797baba02ca8ab5112f9d1c0328cd4b", size = 218040, upload-time = "2025-08-23T14:41:33.472Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ca/5787fb3d7820e66273913affe8209c534ca11241eb34ee8c4fd2aaa9dd87/coverage-7.10.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0913dd1613a33b13c4f84aa6e3f4198c1a21ee28ccb4f674985c1f22109f0aae", size = 259374, upload-time = "2025-08-23T14:41:34.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/89/21af956843896adc2e64fc075eae3c1cadb97ee0a6960733e65e696f32dd/coverage-7.10.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b7181c0feeb06ed8a02da02792f42f829a7b29990fef52eff257fef0885d760", size = 261551, upload-time = "2025-08-23T14:41:36.333Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/390a69244ab837e0ac137989277879a084c786cf036c3c4a3b9637d43a89/coverage-7.10.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d42b7396b605f774d4372dd9c49bed71cbabce4ae1ccd074d155709dd8f235", size = 263776, upload-time = "2025-08-23T14:41:38.25Z" }, + { url = "https://files.pythonhosted.org/packages/00/32/cfd6ae1da0a521723349f3129b2455832fc27d3f8882c07e5b6fefdd0da2/coverage-7.10.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b4fdc777e05c4940b297bf47bf7eedd56a39a61dc23ba798e4b830d585486ca5", size = 261326, upload-time = "2025-08-23T14:41:40.343Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c4/bf8d459fb4ce2201e9243ce6c015936ad283a668774430a3755f467b39d1/coverage-7.10.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:42144e8e346de44a6f1dbd0a56575dd8ab8dfa7e9007da02ea5b1c30ab33a7db", size = 259090, upload-time = "2025-08-23T14:41:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5d/a234f7409896468e5539d42234016045e4015e857488b0b5b5f3f3fa5f2b/coverage-7.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:66c644cbd7aed8fe266d5917e2c9f65458a51cfe5eeff9c05f15b335f697066e", size = 260217, upload-time = "2025-08-23T14:41:43.591Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/87560f036099f46c2ddd235be6476dd5c1d6be6bb57569a9348d43eeecea/coverage-7.10.5-cp313-cp313t-win32.whl", hash = "sha256:2d1b73023854068c44b0c554578a4e1ef1b050ed07cf8b431549e624a29a66ee", size = 220194, upload-time = "2025-08-23T14:41:45.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/a8/04a482594fdd83dc677d4a6c7e2d62135fff5a1573059806b8383fad9071/coverage-7.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:54a1532c8a642d8cc0bd5a9a51f5a9dcc440294fd06e9dda55e743c5ec1a8f14", size = 221258, upload-time = "2025-08-23T14:41:46.44Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ad/7da28594ab66fe2bc720f1bc9b131e62e9b4c6e39f044d9a48d18429cc21/coverage-7.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:74d5b63fe3f5f5d372253a4ef92492c11a4305f3550631beaa432fc9df16fcff", size = 219521, upload-time = "2025-08-23T14:41:47.882Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7f/c8b6e4e664b8a95254c35a6c8dd0bf4db201ec681c169aae2f1256e05c85/coverage-7.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:68c5e0bc5f44f68053369fa0d94459c84548a77660a5f2561c5e5f1e3bed7031", size = 217090, upload-time = "2025-08-23T14:41:49.327Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/3ee14ede30a6e10a94a104d1d0522d5fb909a7c7cac2643d2a79891ff3b9/coverage-7.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cf33134ffae93865e32e1e37df043bef15a5e857d8caebc0099d225c579b0fa3", size = 217365, upload-time = "2025-08-23T14:41:50.796Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/06ac21bf87dfb7620d1f870dfa3c2cae1186ccbcdc50b8b36e27a0d52f50/coverage-7.10.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad8fa9d5193bafcf668231294241302b5e683a0518bf1e33a9a0dfb142ec3031", size = 248413, upload-time = "2025-08-23T14:41:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/cc5bed6e985d3a14228539631573f3863be6a2587381e8bc5fdf786377a1/coverage-7.10.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:146fa1531973d38ab4b689bc764592fe6c2f913e7e80a39e7eeafd11f0ef6db2", size = 250943, upload-time = "2025-08-23T14:41:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/43/6a9fc323c2c75cd80b18d58db4a25dc8487f86dd9070f9592e43e3967363/coverage-7.10.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6013a37b8a4854c478d3219ee8bc2392dea51602dd0803a12d6f6182a0061762", size = 252301, upload-time = "2025-08-23T14:41:56.528Z" }, + { url = "https://files.pythonhosted.org/packages/69/7c/3e791b8845f4cd515275743e3775adb86273576596dc9f02dca37357b4f2/coverage-7.10.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eb90fe20db9c3d930fa2ad7a308207ab5b86bf6a76f54ab6a40be4012d88fcae", size = 250302, upload-time = "2025-08-23T14:41:58.171Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bc/5099c1e1cb0c9ac6491b281babea6ebbf999d949bf4aa8cdf4f2b53505e8/coverage-7.10.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:384b34482272e960c438703cafe63316dfbea124ac62006a455c8410bf2a2262", size = 248237, upload-time = "2025-08-23T14:41:59.703Z" }, + { url = "https://files.pythonhosted.org/packages/7e/51/d346eb750a0b2f1e77f391498b753ea906fde69cc11e4b38dca28c10c88c/coverage-7.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:467dc74bd0a1a7de2bedf8deaf6811f43602cb532bd34d81ffd6038d6d8abe99", size = 249726, upload-time = "2025-08-23T14:42:01.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/85/eebcaa0edafe427e93286b94f56ea7e1280f2c49da0a776a6f37e04481f9/coverage-7.10.5-cp314-cp314-win32.whl", hash = "sha256:556d23d4e6393ca898b2e63a5bca91e9ac2d5fb13299ec286cd69a09a7187fde", size = 219825, upload-time = "2025-08-23T14:42:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f7/6d43e037820742603f1e855feb23463979bf40bd27d0cde1f761dcc66a3e/coverage-7.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:f4446a9547681533c8fa3e3c6cf62121eeee616e6a92bd9201c6edd91beffe13", size = 220618, upload-time = "2025-08-23T14:42:05.037Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b0/ed9432e41424c51509d1da603b0393404b828906236fb87e2c8482a93468/coverage-7.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:5e78bd9cf65da4c303bf663de0d73bf69f81e878bf72a94e9af67137c69b9fe9", size = 219199, upload-time = "2025-08-23T14:42:06.662Z" }, + { url = "https://files.pythonhosted.org/packages/2f/54/5a7ecfa77910f22b659c820f67c16fc1e149ed132ad7117f0364679a8fa9/coverage-7.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5661bf987d91ec756a47c7e5df4fbcb949f39e32f9334ccd3f43233bbb65e508", size = 217833, upload-time = "2025-08-23T14:42:08.262Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/25672d917cc57857d40edf38f0b867fb9627115294e4f92c8fcbbc18598d/coverage-7.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a46473129244db42a720439a26984f8c6f834762fc4573616c1f37f13994b357", size = 218048, upload-time = "2025-08-23T14:42:10.247Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7c/0b2b4f1c6f71885d4d4b2b8608dcfc79057adb7da4143eb17d6260389e42/coverage-7.10.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1f64b8d3415d60f24b058b58d859e9512624bdfa57a2d1f8aff93c1ec45c429b", size = 259549, upload-time = "2025-08-23T14:42:11.811Z" }, + { url = "https://files.pythonhosted.org/packages/94/73/abb8dab1609abec7308d83c6aec547944070526578ee6c833d2da9a0ad42/coverage-7.10.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:44d43de99a9d90b20e0163f9770542357f58860a26e24dc1d924643bd6aa7cb4", size = 261715, upload-time = "2025-08-23T14:42:13.505Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d1/abf31de21ec92731445606b8d5e6fa5144653c2788758fcf1f47adb7159a/coverage-7.10.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a931a87e5ddb6b6404e65443b742cb1c14959622777f2a4efd81fba84f5d91ba", size = 263969, upload-time = "2025-08-23T14:42:15.422Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b3/ef274927f4ebede96056173b620db649cc9cb746c61ffc467946b9d0bc67/coverage-7.10.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9559b906a100029274448f4c8b8b0a127daa4dade5661dfd821b8c188058842", size = 261408, upload-time = "2025-08-23T14:42:16.971Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/83ca2812be616d69b4cdd4e0c62a7bc526d56875e68fd0f79d47c7923584/coverage-7.10.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b08801e25e3b4526ef9ced1aa29344131a8f5213c60c03c18fe4c6170ffa2874", size = 259168, upload-time = "2025-08-23T14:42:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/e0779e5716f72d5c9962e709d09815d02b3b54724e38567308304c3fc9df/coverage-7.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed9749bb8eda35f8b636fb7632f1c62f735a236a5d4edadd8bbcc5ea0542e732", size = 260317, upload-time = "2025-08-23T14:42:20.005Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fe/4247e732f2234bb5eb9984a0888a70980d681f03cbf433ba7b48f08ca5d5/coverage-7.10.5-cp314-cp314t-win32.whl", hash = "sha256:609b60d123fc2cc63ccee6d17e4676699075db72d14ac3c107cc4976d516f2df", size = 220600, upload-time = "2025-08-23T14:42:22.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a0/f294cff6d1034b87839987e5b6ac7385bec599c44d08e0857ac7f164ad0c/coverage-7.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0666cf3d2c1626b5a3463fd5b05f5e21f99e6aec40a3192eee4d07a15970b07f", size = 221714, upload-time = "2025-08-23T14:42:23.616Z" }, + { url = "https://files.pythonhosted.org/packages/23/18/fa1afdc60b5528d17416df440bcbd8fd12da12bfea9da5b6ae0f7a37d0f7/coverage-7.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:bc85eb2d35e760120540afddd3044a5bf69118a91a296a8b3940dfc4fdcfe1e2", size = 219735, upload-time = "2025-08-23T14:42:25.156Z" }, + { url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "ecdsa" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "faker" +version = "37.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/cd/f7679c20f07d9e2013123b7f7e13809a3450a18d938d58e86081a486ea15/faker-37.6.0.tar.gz", hash = "sha256:0f8cc34f30095184adf87c3c24c45b38b33ad81c35ef6eb0a3118f301143012c", size = 1907960, upload-time = "2025-08-26T15:56:27.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/7d/8b50e4ac772719777be33661f4bde320793400a706f5eb214e4de46f093c/faker-37.6.0-py3-none-any.whl", hash = "sha256:3c5209b23d7049d596a51db5d76403a0ccfea6fc294ffa2ecfef6a8843b1e6a7", size = 1949837, upload-time = "2025-08-26T15:56:25.33Z" }, +] + +[[package]] +name = "fakeredis" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/10/c829c3475a26005ebf177057fdf54e2a29025ffc2232d02fb1ae8ac1de68/fakeredis-2.31.0.tar.gz", hash = "sha256:2942a7e7900fd9076ff9e608b9190a87315ac5a325a9ab8bfe288a2d985ecd23", size = 170163, upload-time = "2025-08-11T14:58:20.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/ef/25639beb5d93188b4b6502f601d8f97db77e362774f0183a48e995353c58/fakeredis-2.31.0-py3-none-any.whl", hash = "sha256:2584e57d93df4eb8e87931b29279902826d3caf77d06911106df4e066c2ad198", size = 117666, upload-time = "2025-08-11T14:58:19.03Z" }, +] + +[[package]] +name = "fastapi-slim" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/80/2540fb851fe4b407856ba1cad8b27e48d511413c18a082aafc86f182fb1c/fastapi_slim-0.116.1.tar.gz", hash = "sha256:5dca6046d3a3e35eb733188649a9f883c9d49474b9b3255ecb8ec52a820fbb9f", size = 296505, upload-time = "2025-07-11T16:22:29.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/0c/94090f4ba0ae3b3117d50e3964311f34232ee379814585cbca042b1f2a0a/fastapi_slim-0.116.1-py3-none-any.whl", hash = "sha256:37517a302492c30014979cff8d85f42ae5fbdbe4f8b5d0ceed81745bb3b23149", size = 95683, upload-time = "2025-07-11T16:22:27.379Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mypy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, + { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, + { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, + { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, + { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "passlib" +version = "1.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-jose" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ecdsa" }, + { name = "pyasn1" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, + { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqladmin" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "python-multipart" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "wtforms" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/0c/614041e1b544e0de1f43b58f0105b3e2795b80369d5b0ff7412882d42fff/sqladmin-0.21.0.tar.gz", hash = "sha256:cb455b79eb79ef7d904680dd83817bf7750675147400b5b7cc401d04bda7ef2c", size = 1428312, upload-time = "2025-07-02T09:41:21.207Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/8d/81b2a48cc6f5479cb1148292518e3006ec8f5fbe3b0829ef165984e9d7b9/sqladmin-0.21.0-py3-none-any.whl", hash = "sha256:2b1802c49bdd3128c6452625705693cf32d5d33e7db30e63f409bd20a9c05b53", size = 1443585, upload-time = "2025-07-02T09:41:19.205Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/db/20c78f1081446095450bdc6ee6cc10045fce67a8e003a5876b6eaafc5cc4/sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24", size = 2134891, upload-time = "2025-08-11T15:51:13.019Z" }, + { url = "https://files.pythonhosted.org/packages/45/0a/3d89034ae62b200b4396f0f95319f7d86e9945ee64d2343dcad857150fa2/sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83", size = 2123061, upload-time = "2025-08-11T15:51:14.319Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/2711f7ff1805919221ad5bee205971254845c069ee2e7036847103ca1e4c/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9", size = 3320384, upload-time = "2025-08-11T15:52:35.088Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0e/3d155e264d2ed2778484006ef04647bc63f55b3e2d12e6a4f787747b5900/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48", size = 3329648, upload-time = "2025-08-11T15:56:34.153Z" }, + { url = "https://files.pythonhosted.org/packages/5b/81/635100fb19725c931622c673900da5efb1595c96ff5b441e07e3dd61f2be/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687", size = 3258030, upload-time = "2025-08-11T15:52:36.933Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/a99302716d62b4965fded12520c1cbb189f99b17a6d8cf77611d21442e47/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe", size = 3294469, upload-time = "2025-08-11T15:56:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a2/3a11b06715149bf3310b55a98b5c1e84a42cfb949a7b800bc75cb4e33abc/sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d", size = 2098906, upload-time = "2025-08-11T15:55:00.645Z" }, + { url = "https://files.pythonhosted.org/packages/bc/09/405c915a974814b90aa591280623adc6ad6b322f61fd5cff80aeaef216c9/sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a", size = 2126260, upload-time = "2025-08-11T15:55:02.965Z" }, + { url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780, upload-time = "2025-03-07T05:43:32.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622, upload-time = "2025-03-07T05:43:30.37Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, +] + +[[package]] +name = "typer" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/78/d90f616bf5f88f8710ad067c1f8705bf7618059836ca084e5bb2a0855d75/typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614", size = 102836, upload-time = "2025-08-18T19:18:22.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/76/06dbe78f39b2203d2a47d5facc5df5102d0561e2807396471b5f7c5a30a1/typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9", size = 46397, upload-time = "2025-08-18T19:18:21.663Z" }, +] + +[[package]] +name = "types-passlib" +version = "1.7.7.20250602" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/3e/501a5832130e5f93450b1e02090e2ee27a37135d11378a47debf960e3131/types_passlib-1.7.7.20250602.tar.gz", hash = "sha256:cf2350e78d36b6b09e4db44284d96651b57285f499cfabf111b616065abab7b3", size = 25406, upload-time = "2025-06-02T03:14:56.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/fc/530236c21f1a0be84c42b23c91c250ef96404c475b739ac4479430ebd7d4/types_passlib-1.7.7.20250602-py3-none-any.whl", hash = "sha256:ed73a91be9a22484ebd62cc0d127675ded542b892b99776db92dab760bbfe274", size = 40410, upload-time = "2025-06-02T03:14:54.834Z" }, +] + +[[package]] +name = "types-pyasn1" +version = "0.6.0.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/9b/5c6e6690da87e9fec317925d4a9500a8d61c2e2c1ec39de46736f76a29e8/types_pyasn1-0.6.0.20250516.tar.gz", hash = "sha256:1a9b35a4f033cd70c384a5043a3407b2cc07afc95900732b66e0d38426c7541d", size = 17153, upload-time = "2025-05-16T03:07:23.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/56/0d10029d4d943226f044cec3f3d1f9ad54acb4884ee0b78e5d7b66c07d9d/types_pyasn1-0.6.0.20250516-py3-none-any.whl", hash = "sha256:b9925e4e22e09eed758b93b6f2a7881b89d842c2373dd11c09b173567d170142", size = 24093, upload-time = "2025-05-16T03:07:22.759Z" }, +] + +[[package]] +name = "types-python-jose" +version = "3.5.0.20250531" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/c8/09095e22b8e5eb3992f47722a3e1b31098b55c5e8325f4b21c5f1bdcb06b/types_python_jose-3.5.0.20250531.tar.gz", hash = "sha256:dbac2bc99fbb8124068696617f8709acfe4a43d79c6df3e59800006d46d621fe", size = 11891, upload-time = "2025-05-31T03:04:29.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/33/9d8c351a44e68896a53003e00fb01e1158b9e5b68cf3b75c1e4b51eb5263/types_python_jose-3.5.0.20250531-py3-none-any.whl", hash = "sha256:1609ee4d40a8a2ef5f62fcda99ec977b2ae773dfee9355cfb7e5002afa063c55", size = 14725, upload-time = "2025-05-31T03:04:27.802Z" }, +] + +[[package]] +name = "types-wtforms" +version = "3.2.1.20250809" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/f7/72ca8564a59f118dd5ce5b6afcdbb38c297d04950dcaf49873dae8c6ff45/types_wtforms-3.2.1.20250809.tar.gz", hash = "sha256:9108399333be3bde66179f69a610a5d05c25485024f698d5adc3f6a9df20d029", size = 17194, upload-time = "2025-08-09T03:17:24.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/5a/ecedecb8a1ccff7ea936fb77a359f8295da36fb7e7985cb4fc147110a091/types_wtforms-3.2.1.20250809-py3-none-any.whl", hash = "sha256:d254cf027c6725e21ad50044da692d57586dd29f38abd3a6b4ec10993f6741cc", size = 24294, upload-time = "2025-08-09T03:17:24.008Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "wtforms" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/c7/96d10183c3470f1836846f7b9527d6cb0b6c2226ebca40f36fa29f23de60/wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9", size = 134705, upload-time = "2024-01-06T07:52:41.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/19/c3232f35e24dccfad372e9f341c4f3a1166ae7c66e4e1351a9467c921cc1/wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07", size = 145961, upload-time = "2024-01-06T07:52:43.023Z" }, +] From fa97538c664a0c4aa57f137acb022d4fffe42c54 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:39:36 +0300 Subject: [PATCH 04/68] Sync configs for Ruff, Mypy and Pytest --- pyproject.toml | 141 +++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 75 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66246e3..9a6d03c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,85 +1,76 @@ -[tool.poetry] -name = "fastapi-auth-webservice" -version = "0.1.0" -description = "Auth API based on JWT" -authors = ["Joe Doe "] +[project] +name = "authapigateway" +version = "0.0.1" +description = "Cookie-based JWT Auth API" readme = "README.md" -packages = [{include = "fastapi_auth_webservice"}] - -[tool.poetry.dependencies] -python = "^3.12" -fastapi-slim = "~0.111" -alembic = "~1.13.1" -asyncpg = "~0.29" -bcrypt = "~4.0.1" -email-validator = "~2.1.1" -gunicorn = "~22.0" -python-jose = "~3.3" -python-multipart = "0.0.9" -redis = "~5.0.5" -sqlalchemy = "~2.0" -uvicorn = "~0.30.1" -passlib = "~1.7.4" -typer = "^0.12.3" -itsdangerous = "~2.2" -sqladmin = "~0.18" +requires-python = ">=3.12" +dependencies = [ + "alembic>=1.16.4", + "asyncpg>=0.30.0", + "bcrypt==4.0.1", + "email-validator>=2.3.0", + "fastapi-slim>=0.116.1", + "gunicorn>=23.0.0", + "itsdangerous>=2.2.0", + "passlib>=1.7.4", + "pydantic-settings>=2.10.1", + "python-jose>=3.5.0", + "python-multipart>=0.0.20", + "redis>=6.4.0", + "sqladmin>=0.21.0", + "sqlmodel>=0.0.24", + "typer>=0.16.1", + "uvicorn>=0.35.0", +] -[tool.poetry.group.dev.dependencies] -aiosqlite = "~0.20" -coverage = "~7.5.3" -fakeredis = "~2.23.2" -pytest = "~8.2" -pytest-cov = "~5.0" -mypy = "~1.10" -ruff = "~0.4.8" -pytest-faker = "^2.0.0" -httpx = "^0.27" +[dependency-groups] +test = [ + "aiosqlite>=0.21.0", + "coverage>=7.10.5", + "faker>=37.6.0", + "fakeredis>=2.31.0", + "httpx>=0.28.1", + "mypy>=1.17.1", + "pytest>=8.4.1", + "pytest-cov>=6.2.1", + "ruff>=0.12.10", + "types-passlib>=1.7.7.20250602", + "types-python-jose>=3.5.0.20250531", + "types-wtforms>=3.2.1.20250809", +] [tool.ruff] -target-version = "py311" -line-length = 100 -indent-width = 2 -exclude = [ - ".git", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - ".vscode", - "venv", - "nginx", - "scripts", - "src/alembic/versions" +target-version = "py312" +exclude = [".venv"] +lint.extend-select = ["ALL"] +lint.ignore = [ + "COM812", + "D100", + "D104", + "D204", + "ERA001", + "FBT001", + "FBT002", + "SLF001", + "UP040" ] -[tool.ruff.lint] -select = ["E501"] - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" +[tool.ruff.lint.per-file-ignores] +"src/tests/*" = ["D103", "S101"] +"src/app/user/exceptions.py" = ["D101"] [tool.mypy] python_version = "3.12" -check_untyped_defs = true -disallow_untyped_calls = true -disallow_untyped_defs = true -ignore_missing_imports = true -warn_unused_ignores = true -warn_unreachable = true -exclude = [ - ".git", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - ".vscode", - "venv", - "nginx", - "scripts", - "src/alembic/versions" -] +strict = true +exclude = [".venv"] -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.pytest.ini_options] +addopts = """ + --durations=10 + --durations-min=0.1 + --verbose + --maxfail=1 +""" +filterwarnings = [ + "ignore::DeprecationWarning", +] \ No newline at end of file From 83e2567e1169111a90a4e90cb719dc44110055dc Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:40:02 +0300 Subject: [PATCH 05/68] Update steps to build the image --- Dockerfile | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5171a8b..cfa21ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,13 @@ -FROM python:3.12.4-slim AS builder - -ARG DEV=false - -COPY pyproject.toml poetry.lock / - -RUN pip install poetry && \ - POETRY_CMD="poetry export -f requirements.txt --output requirements.txt --without-hashes"; \ - if [ "$DEV" = "true" ]; then \ - POETRY_CMD="$POETRY_CMD --with dev"; \ - fi; \ - $POETRY_CMD && \ - pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt - - -FROM python:3.12.4-slim - +FROM ghcr.io/astral-sh/uv:python3.12-alpine ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=utf-8 \ - PYTHONPATH=/app - -WORKDIR /app - -COPY --from=builder /wheels /wheels -COPY ./src . - -RUN chmod +x ./scripts/run.sh && \ - pip install --no-cache /wheels/* && \ - rm -rf /wheels - -CMD ["./scripts/run.sh"] + PATH="/src/.venv/bin:$PATH" \ + PYTHONPATH="/src" +WORKDIR /src +COPY ./pyproject.toml ./uv.lock ./run.sh ./ +RUN apk add --no-cache curl && \ + uv sync --locked && \ + chmod +x ./run.sh +COPY ./src ./gunicorn.conf.py ./ +CMD ["./run.sh"] From bf8c3143a78871f0b9f57f127dbde390ad1cd346 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:40:30 +0300 Subject: [PATCH 06/68] Update app config template --- .env.example | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 614f7dc..92d7829 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,11 @@ -APP_HOST=auth +APP_HOST=app APP_PORT=8000 DEBUG=1 -ADMIN_DEBUG=1 -GUNICORN_WORKERS=5 -GUNICORN_THREADS=2 +GUNICORN_WORKERS=2 POSTGRES_USER=user -POSTGRES_PASSWORD=123456 +POSTGRES_PASSWORD=12345 POSTGRES_DB=user POSTGRES_HOST=db POSTGRES_PORT=5432 @@ -16,7 +14,7 @@ DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGR REDIS_HOST=redis REDIS_PORT=6379 REDIS_USER=default -REDIS_PASSWORD=secret123 +REDIS_PASSWORD=123456 REDIS_URL=redis://${REDIS_USER}:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT} NGINX_PORT=80 From f40488e2248315cbdfdf68b5849e687a029ac680 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:41:06 +0300 Subject: [PATCH 07/68] Add location to host static files --- proxy/default.conf.tpl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/proxy/default.conf.tpl b/proxy/default.conf.tpl index cc16f52..f296307 100644 --- a/proxy/default.conf.tpl +++ b/proxy/default.conf.tpl @@ -7,6 +7,10 @@ server { server_name ${NGINX_SERVER_HOST}; client_max_body_size 10M; + location /admin/statics/ { + alias /api/statics/; + } + location /admin { include /etc/nginx/snippets/auth_subrequest.conf; proxy_pass http://auth_api/admin; @@ -14,13 +18,11 @@ server { } location ~ ^/openapi.json { - # include /etc/nginx/snippets/auth_subrequest.conf; set $openapi "openapi.json"; proxy_pass http://auth_api/$openapi; } location = /docs { - # include /etc/nginx/snippets/auth_subrequest.conf; proxy_pass http://auth_api/docs; } From dfb2eec3a15b7bdec6a0b1b9a74259e7df774676 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:41:33 +0300 Subject: [PATCH 08/68] Update policy for regular users --- src/policy.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/policy.json b/src/policy.json index 56a7e58..4200d77 100644 --- a/src/policy.json +++ b/src/policy.json @@ -17,9 +17,10 @@ }, "user": { "locations": [ + "/docs", "/login", "/logout", "/signup" ] } -} +} \ No newline at end of file From ebea86fd4b40089686e88ca497b6bc0d03fe0424 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:41:56 +0300 Subject: [PATCH 09/68] Add __init__.py --- src/scripts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/scripts/__init__.py diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py new file mode 100644 index 0000000..e69de29 From 32c8b2f6af8149ecf6424b7806fb8a1218593734 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:42:17 +0300 Subject: [PATCH 10/68] Update interface to typer --- src/scripts/create_user.py | 54 ++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/scripts/create_user.py b/src/scripts/create_user.py index 8145821..a014d3f 100644 --- a/src/scripts/create_user.py +++ b/src/scripts/create_user.py @@ -3,40 +3,22 @@ import typer from app.main.db import async_session -from app.user.constants import UserRole as UR +from app.user.constants import UserRole from app.user.models import User from app.user.schemas import NewUser +app = typer.Typer() -async def create_user(user: NewUser, role: UR) -> None: - """ - Save info about user into the database. - - :param user: info about user to be saved - :return: None - """ - async with async_session() as db: - await User.create(db, user, hashed_password=user._hashed_password, role=role.name) - -def main( - first_name: str = typer.Option(..., prompt=True), - last_name: str = typer.Option(..., prompt=True), - email: str = typer.Option(..., prompt=True), - username: str = typer.Option(..., prompt=True), - role: UR = typer.Option(..., prompt=True), - password: str = typer.Option(..., prompt=True, confirmation_prompt=True, hide_input=True), -) -> None: - """ - CLI command to create a new user. - - :param first_name: first name of the user - :param last_name: last name of the user - :param email: email of the user - :param username: username of the user - :param role: role of the user - :param password: password of the user - """ +@app.command() +def create_user() -> None: + """Create a new user.""" + first_name = typer.prompt("First name") + last_name = typer.prompt("Last name") + email = typer.prompt("Email") + username = typer.prompt("Username") + role = typer.prompt("Role", type=UserRole) + password = typer.prompt("Password", hide_input=True, confirmation_prompt=True) user = NewUser( first_name=first_name, last_name=last_name, @@ -45,8 +27,18 @@ def main( password=password, repeat_password=password, ) - asyncio.run(create_user(user, role)) + asyncio.run(save_user(user, role)) + + +async def save_user(user: NewUser, role: UserRole) -> None: + """Save info about user into the database. + + :param user: info about user to be saved + :return: None + """ + async with async_session() as db: + await User.create(db, user, role=role.name) if __name__ == "__main__": - typer.run(main) + app() From e98f12284f120aedc16562127a93f9fedf5094e3 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 14:43:24 +0300 Subject: [PATCH 11/68] Update database migrations file --- .../versions/011e020328f4_init_commit.py | 48 --------------- .../versions/986ea1a0e2e2_init_commit.py | 60 +++++++++++++++++++ 2 files changed, 60 insertions(+), 48 deletions(-) delete mode 100644 src/alembic/versions/011e020328f4_init_commit.py create mode 100644 src/alembic/versions/986ea1a0e2e2_init_commit.py diff --git a/src/alembic/versions/011e020328f4_init_commit.py b/src/alembic/versions/011e020328f4_init_commit.py deleted file mode 100644 index 563e8a3..0000000 --- a/src/alembic/versions/011e020328f4_init_commit.py +++ /dev/null @@ -1,48 +0,0 @@ -"""init commit - -Revision ID: 011e020328f4 -Revises: -Create Date: 2024-06-29 10:08:38.326406 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '011e020328f4' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('users', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('username', sa.String(length=255), nullable=False), - sa.Column('email', sa.String(length=255), nullable=False), - sa.Column('first_name', sa.String(length=255), nullable=True), - sa.Column('last_name', sa.String(length=255), nullable=True), - sa.Column('hashed_password', sa.String(length=255), nullable=False), - sa.Column('role', sa.String(length=255), nullable=False), - sa.Column('is_active', sa.Boolean(), server_default=sa.text('true'), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) - op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=True) - op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_users_username'), table_name='users') - op.drop_index(op.f('ix_users_id'), table_name='users') - op.drop_index(op.f('ix_users_email'), table_name='users') - op.drop_table('users') - # ### end Alembic commands ### diff --git a/src/alembic/versions/986ea1a0e2e2_init_commit.py b/src/alembic/versions/986ea1a0e2e2_init_commit.py new file mode 100644 index 0000000..4556069 --- /dev/null +++ b/src/alembic/versions/986ea1a0e2e2_init_commit.py @@ -0,0 +1,60 @@ +"""init commit + +Revision ID: 986ea1a0e2e2 +Revises: +Create Date: 2025-08-27 14:35:17.948913 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "986ea1a0e2e2" +down_revision: str | None = None +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=False), + sa.Column("first_name", sa.String(), nullable=False), + sa.Column("last_name", sa.String(), nullable=False), + sa.Column("password", sa.String(), nullable=False), + sa.Column("role", sa.String(), nullable=False), + sa.Column("is_active", sa.Boolean(), default=True, nullable=False), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.DateTime(timezone=True), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True) + op.create_index(op.f("ix_users_id"), "users", ["id"], unique=True) + op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_users_username"), table_name="users") + op.drop_index(op.f("ix_users_id"), table_name="users") + op.drop_index(op.f("ix_users_email"), table_name="users") + op.drop_table("users") + # ### end Alembic commands ### From 45e9f70d1c984fe3cf9140e7848285469a3fa68f Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:47:49 +0300 Subject: [PATCH 12/68] Changed class for enum-based constatns to inherit --- src/alembic/env.py | 16 +++++++++------- src/app/user/constants.py | 21 ++++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/alembic/env.py b/src/alembic/env.py index 44228d8..9cc3876 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -1,17 +1,16 @@ -import asyncio +import asyncio # noqa: INP001 from logging.config import fileConfig -from alembic import context from sqlalchemy.engine.base import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config from sqlalchemy.pool import NullPool -from sqlalchemy.ext.asyncio import async_engine_from_config -from app.main.settings import DATABASE_URL +from alembic import context +from app.main.settings import settings from app.user.models import User - config = context.config -config.set_main_option("sqlalchemy.url", DATABASE_URL) +config.set_main_option("sqlalchemy.url", settings.database_url) if config.config_file_name is not None: fileConfig(config.config_file_name) @@ -40,11 +39,14 @@ def run_migrations_offline() -> None: with context.begin_transaction(): context.run_migrations() + def do_run_migrations(connection: Connection) -> None: + """Run migrations.""" context.configure(connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations() + async def run_migrations_online() -> None: """Run migrations in 'online' mode. @@ -52,7 +54,7 @@ async def run_migrations_online() -> None: and associate a connection with the context. """ connectable = async_engine_from_config( - config.get_section(config.config_ini_section), # type: ignore + config.get_section(config.config_ini_section), # type: ignore prefix="sqlalchemy.", poolclass=NullPool, ) diff --git a/src/app/user/constants.py b/src/app/user/constants.py index 49e9f18..a256985 100644 --- a/src/app/user/constants.py +++ b/src/app/user/constants.py @@ -1,20 +1,23 @@ -from enum import Enum +from enum import StrEnum -class TokenType(str, Enum): - """Types of JWT tokens""" +class TokenType(StrEnum): + """Types of JWT tokens.""" + access = "access" refresh = "refresh" -class CookieType(str, Enum): - """Types of cookies""" - access_token = "access_token" - refresh_token = "refresh_token" +class CookieType(StrEnum): + """Types of cookies.""" + + access_token = "access_token" # noqa: S105 + refresh_token = "refresh_token" # noqa: S105 + +class UserRole(StrEnum): + """User roles of the app.""" -class UserRole(str, Enum): - """User roles of the app""" admin = "admin" moderator = "moderator" user = "user" From dae00e2bcb9eba699bbafb55b0ae4920fd1f1a53 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:48:39 +0300 Subject: [PATCH 13/68] Update list of attributes of the 'UserView' --- src/app/admin/views.py | 89 ++++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/src/app/admin/views.py b/src/app/admin/views.py index c0c6c32..abf3bb3 100644 --- a/src/app/admin/views.py +++ b/src/app/admin/views.py @@ -1,43 +1,82 @@ -from typing import Any -from sqladmin import ModelView +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, ClassVar -from starlette.requests import Request -from wtforms.fields import SelectField +from sqladmin import ModelView +from wtforms.fields import EmailField, Field, PasswordField, SelectField -from app.user.constants import UserRole as UR from app.user.auth import generate_password_hash +from app.user.constants import UserRole from app.user.models import User +if TYPE_CHECKING: + from sqladmin._types import MODEL_ATTR + from starlette.requests import Request + class UserView(ModelView, model=User): + """View for admin UI to display info about users from the database.""" + icon = "fa-solid fa-user" - column_labels = {"hashed_password": "Password"} - column_list = [ - User.first_name, - User.last_name, - User.username, - User.email, - User.role, - User.is_active, + column_list: ClassVar[Sequence["MODEL_ATTR"]] = [ + "first_name", + "last_name", + "username", + "email", + "role", + "is_active", ] - form_edit_rules = [ - "first_name", "last_name", "username", "email", "is_active", "role", + form_edit_rules: ClassVar[list[str]] = [ + "first_name", + "last_name", + "username", + "email", + "role", + "is_active", ] - form_create_rules = [ - "first_name", "last_name", "username", "email", "role", "hashed_password", + form_create_rules: ClassVar[list[str]] = [ + "first_name", + "last_name", + "username", + "email", + "role", + "password", ] - column_details_list = [User.id, *column_list, User.created_at, User.updated_at] - column_sortable_list = [User.role, User.is_active, User.created_at] - column_searchable_list = [User.username] - form_overrides = {"role": SelectField} - form_args = {"role": {"choices": [(user.value, user.value) for user in UR]}} + column_details_list: ClassVar[Sequence["MODEL_ATTR"]] = [ + "id", + *column_list, + "created_at", + "updated_at", + ] + column_sortable_list: ClassVar[Sequence["MODEL_ATTR"]] = [ + "role", + "is_active", + "created_at", + ] + column_searchable_list: ClassVar[str | Sequence["MODEL_ATTR"]] = ["username"] + form_overrides: ClassVar[dict[str, type[Field]]] = { + "email": EmailField, + "password": PasswordField, + "role": SelectField, + } + form_args: ClassVar[dict[str, dict[str, Any]]] = { + "role": {"choices": [(user.value, user.value) for user in UserRole]} + } async def on_model_change( self, data: dict[str, Any], - model: User, + model: User, # noqa: ARG002 is_created: bool, - request: Request, + request: "Request", # noqa: ARG002 ) -> None: + """Create / Update actions on the user object. + + Create actions: + - create the user, hashing the password; + - + + Update actions: + - + """ if is_created: - data["hashed_password"] = generate_password_hash(data["hashed_password"]) + data["password"] = generate_password_hash(data["password"]) From 895222646d20115d8f566696eeebf5f009b9ab5e Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:49:15 +0300 Subject: [PATCH 14/68] Add settings to the main Admin app --- src/app/admin/app.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/app/admin/app.py b/src/app/admin/app.py index 2eb6adb..e0a21c7 100644 --- a/src/app/admin/app.py +++ b/src/app/admin/app.py @@ -1,25 +1,23 @@ -from __future__ import annotations - from typing import TYPE_CHECKING from sqladmin import Admin +from app.main.settings import settings + +from .views import UserView + if TYPE_CHECKING: from fastapi import FastAPI from sqlalchemy.ext.asyncio.engine import AsyncEngine -def init_admin_app(app: FastAPI, db: AsyncEngine) -> Admin: - """ - Initialize admin app. +def init_admin_app(app: "FastAPI", db: "AsyncEngine") -> Admin: + """Init admin app. :param app: FastAPI app - :param db: database engine + :param db: db engine :return: admin app """ - from .settings import ADMIN_DEBUG - from .views import UserView - - admin = Admin(app, db, debug=ADMIN_DEBUG) + admin = Admin(app, db, **settings.admin_app_kwargs) admin.add_view(UserView) return admin From b659dcf7ecee365fe638a83ff5d5a5e8e00eef96 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:49:42 +0300 Subject: [PATCH 15/68] Set default user role --- src/scripts/create_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/create_user.py b/src/scripts/create_user.py index a014d3f..4ffaf01 100644 --- a/src/scripts/create_user.py +++ b/src/scripts/create_user.py @@ -17,7 +17,7 @@ def create_user() -> None: last_name = typer.prompt("Last name") email = typer.prompt("Email") username = typer.prompt("Username") - role = typer.prompt("Role", type=UserRole) + role = typer.prompt("Role", type=UserRole, default=UserRole.user) password = typer.prompt("Password", hide_input=True, confirmation_prompt=True) user = NewUser( first_name=first_name, From a39d9e962e7d0cca79634011c840e6c35b771f04 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:50:18 +0300 Subject: [PATCH 16/68] Update clients for E2E tests --- src/tests/app/admin/test_views.py | 84 +++++++++++-------------------- 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/src/tests/app/admin/test_views.py b/src/tests/app/admin/test_views.py index 69a5ac7..d031540 100644 --- a/src/tests/app/admin/test_views.py +++ b/src/tests/app/admin/test_views.py @@ -1,73 +1,45 @@ +from typing import TYPE_CHECKING + import pytest from fastapi import status from httpx import AsyncClient +from tests.conftest import E2E_MODE_DISABLED -@pytest.mark.anyio -async def test_admin_home(client: AsyncClient) -> None: - resp = await client.get("/admin/") - assert resp.status_code == status.HTTP_200_OK - - -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_home_as_unauthorized_user(e2e_client: AsyncClient) -> None: - resp = await e2e_client.get("/admin/") - assert resp.status_code == status.HTTP_401_UNAUTHORIZED - - -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_home_as_authorized_user(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/") - assert response.status_code == status.HTTP_403_FORBIDDEN +if TYPE_CHECKING: + from httpx import AsyncClient +pytestmark = pytest.mark.anyio -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_home_as_authorized_moderator(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/") - assert response.status_code == status.HTTP_403_FORBIDDEN - -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_home_as_authorized_admin(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/") - assert response.status_code == status.HTTP_200_OK - - -# ================================================================================= -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_users_list(client: AsyncClient) -> None: - home_resp = await client.get("/admin/user/list") - assert home_resp.status_code == status.HTTP_200_OK +async def test_admin_home(client: "AsyncClient") -> None: + resp = await client.get("/admin/") + assert resp.status_code == status.HTTP_200_OK -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_users_list_as_unauthorized_user(e2e_client: AsyncClient) -> None: - resp = await e2e_client.get("/admin/user/list") +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_admin_home_as_unauthorized_user(e2e_client: "AsyncClient") -> None: + resp = await e2e_client.get("/admin/") assert resp.status_code == status.HTTP_401_UNAUTHORIZED -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_users_list_as_authorized_user(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/user/list") - assert response.status_code == status.HTTP_403_FORBIDDEN +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_admin_home_as_authorized_user( + authorized_user_client: "AsyncClient", +) -> None: + resp = await authorized_user_client.get("/admin/") + assert resp.status_code == status.HTTP_403_FORBIDDEN -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_users_list_as_authorized_moderator(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/user/list") - assert response.status_code == status.HTTP_403_FORBIDDEN +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_admin_home_as_moderator( + authorized_moderator_client: "AsyncClient", +) -> None: + resp = await authorized_moderator_client.get("/admin/") + assert resp.status_code == status.HTTP_403_FORBIDDEN -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_admin_users_list_as_authorized_admin(e2e_client: AsyncClient) -> None: - response = await e2e_client.get("/admin/user/list") - assert response.status_code == status.HTTP_200_OK +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_admin_home_as_admin(e2e_client: "AsyncClient") -> None: + resp = await e2e_client.get("/admin/") + assert resp.status_code == status.HTTP_200_OK From 19a77f386d2939a9cdb6f1037e91ff26e7bdb49b Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:50:33 +0300 Subject: [PATCH 17/68] Update clients for E2E tests --- src/tests/app/main/test_routes.py | 52 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/tests/app/main/test_routes.py b/src/tests/app/main/test_routes.py index 8c6f751..8aeaac7 100644 --- a/src/tests/app/main/test_routes.py +++ b/src/tests/app/main/test_routes.py @@ -1,38 +1,44 @@ +from typing import TYPE_CHECKING + import pytest from fastapi import status -from httpx import AsyncClient +from tests.conftest import E2E_MODE_DISABLED + +if TYPE_CHECKING: + from httpx import AsyncClient + +pytestmark = pytest.mark.anyio -@pytest.mark.anyio -async def test_health(client: AsyncClient) -> None: + +async def test_health(client: "AsyncClient") -> None: resp = await client.get("/health") - assert resp.status_code == status.HTTP_200_OK - assert resp.json() == {"status": "OK"} + assert resp.status_code == status.HTTP_204_NO_CONTENT -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_health_as_unauthorized_user(e2e_client: AsyncClient) -> None: +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_health_as_unauthorized_user(e2e_client: "AsyncClient") -> None: resp = await e2e_client.get("/health") assert resp.status_code == status.HTTP_401_UNAUTHORIZED -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_health_as_authorized_user(e2e_client: AsyncClient) -> None: - resp = await e2e_client.get("/health") - assert resp.status_code == status.HTTP_401_UNAUTHORIZED +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_health_as_authorized_user( + authorized_user_client: "AsyncClient", +) -> None: + resp = await authorized_user_client.get("/health") + assert resp.status_code == status.HTTP_403_FORBIDDEN -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_health_as_authorized_moderator(e2e_client: AsyncClient) -> None: - resp = await e2e_client.get("/health") - assert resp.status_code == status.HTTP_401_UNAUTHORIZED +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_health_as_moderator( + authorized_moderator_client: "AsyncClient", +) -> None: + resp = await authorized_moderator_client.get("/health") + assert resp.status_code == status.HTTP_403_FORBIDDEN -@pytest.mark.skip(reason="Not Implemented") -@pytest.mark.anyio -async def test_health_as_authorized_admin(e2e_client: AsyncClient) -> None: - resp = await e2e_client.get("/health") - assert resp.status_code == status.HTTP_200_OK +@pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") +async def test_health_as_admin(authorized_admin_client: "AsyncClient") -> None: + resp = await authorized_admin_client.get("/health") + assert resp.status_code == status.HTTP_204_NO_CONTENT From 9688fd16eaf507f4393a1a5daf2327274c80cd1f Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:51:10 +0300 Subject: [PATCH 18/68] Add separate clients with different roles for E2E tests --- src/tests/conftest.py | 264 +++++++++++++++++++++++++++++++----------- 1 file changed, 194 insertions(+), 70 deletions(-) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index f7ccb2b..c143d34 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -1,115 +1,239 @@ -from typing import AsyncIterator, Callable, TypedDict +import logging +import os +from collections.abc import AsyncIterator +from typing import TYPE_CHECKING import pytest -from faker.proxy import Faker from fakeredis import FakeAsyncRedis -from httpx import AsyncClient, ASGITransport, Cookies, Response -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from fastapi import status +from httpx import ASGITransport, AsyncClient, Cookies +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) from app.main.app import app from app.main.db import Base -from app.main.dependencies import get_db -from app.main.settings import SERVER_DOMAIN +from app.main.dependencies import get_db, get_redis +from app.main.settings import settings +from app.user.constants import UserRole +from app.user.models import User +from app.user.schemas import NewUser +if TYPE_CHECKING: + from faker import Faker + from redis.asyncio import Redis -class RandomUser(TypedDict): - """Structure to keep info about random user to signup""" - username: str - email: str - password: str - repeat_password: str - first_name: str - last_name: str +# End-to-end mode should be enabled when running end-to-end tests +E2E_MODE_DISABLED = bool(int(os.getenv("E2E_MODE_DISABLED", "1"))) -DATABASE_URL = "sqlite+aiosqlite:///:memory:" +engine = create_async_engine(settings.test_database_url) +async_session = async_sessionmaker( + engine, + class_=AsyncSession, + expire_on_commit=False, +) -test_engine = create_async_engine(DATABASE_URL) -async_session = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) + +@pytest.fixture(scope="session") +def anyio_backend() -> str: + """Backend (asyncio) for pytest to run async tests.""" + return "asyncio" @pytest.fixture(scope="session", autouse=True) -async def migrate_db() -> AsyncIterator: - """Create and drop the database for testing on startup and shutdown""" - async with test_engine.begin() as conn: +async def migrate_db() -> AsyncIterator[None]: + """Create and drop test test db on startup and shutdown.""" + async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield - async with test_engine.begin() as conn: + async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) async def override_get_db() -> AsyncIterator[AsyncSession]: - """Override dependency for API routes to interact with the database""" + """Override dependency for API routes to interact with db.""" async with async_session() as session: yield session -app.state.redis = FakeAsyncRedis() -app.dependency_overrides[get_db] = override_get_db +@pytest.fixture(scope="session") +async def redis_client() -> AsyncIterator["Redis"]: + """Fixture to provide fake redis client object.""" + async with FakeAsyncRedis() as r: + yield r @pytest.fixture(scope="session") -def anyio_backend() -> str: - """Internal pytest's fixture to specify backend to run async tests""" - return "asyncio" - +async def client(redis_client: "Redis") -> AsyncIterator[AsyncClient]: + """Fixture to provide an HTTP-client for integration tests. -@pytest.fixture(scope="function") -def faker() -> Faker: - """Overrides pytest-faker fixture providing a new instance for each call""" - return Faker() + :param redis_client: fixture providing a client for Redis. + :yield: async HTTP-client. + """ + async def override_get_redis() -> "Redis": + """Override dependency for API routes to interact with Redis.""" + return redis_client -@pytest.fixture(scope="session") -async def client() -> AsyncIterator[AsyncClient]: - """Async HTTP-client for integration tests""" - base_url = f"http://{SERVER_DOMAIN}" - transport = ASGITransport(app) # type: ignore - async with AsyncClient(base_url=base_url, transport=transport) as _client: - yield _client + # use a different logger specifically for testing + app.state.logger = logging.getLogger(settings.test_log_name) + app.dependency_overrides[get_db] = override_get_db + app.dependency_overrides[get_redis] = override_get_redis + transport = ASGITransport(app) + base_url = f"http://{settings.host_server_domain}" + async with AsyncClient(base_url=base_url, transport=transport) as ac: + yield ac @pytest.fixture(scope="session") async def e2e_client() -> AsyncIterator[AsyncClient]: - """Async HTTP-client for E2E test""" - base_url = f"http://{SERVER_DOMAIN}" - async with AsyncClient(base_url=base_url) as _client: - yield _client - - -@pytest.fixture(scope="function") -def random_user(faker: Faker) -> RandomUser: - """Randm user data""" - user_password = faker.password() - return RandomUser( + """Fixture to provide an HTTP-client for end-to-end tests.""" + # use a different logger specifically for testing + app.state.logger = logging.getLogger(settings.test_log_name) + base_url = f"http://{settings.host_server_domain}" + async with AsyncClient(base_url=base_url) as ac: + yield ac + + +@pytest.fixture +def user(faker: "Faker") -> NewUser: + """Fixture to generate random user data. + + :param faker: faker fixture + :return: user with random info. + """ + # ensure randomness for each call + faker.seed_instance() + password = faker.password() + return NewUser.model_construct( username=faker.user_name(), email=faker.email(), - password=user_password, - repeat_password=user_password, + password=password, + repeat_password=password, first_name=faker.first_name(), - last_name=faker.last_name() + last_name=faker.last_name(), ) -@pytest.fixture(scope="session") -def copy_cookies() -> Callable[[Response], Cookies]: - def _copy_cookies(resp: Response) -> Cookies: - """Copy cookies from the response for further reusage""" - return Cookies({c.name: c.value or "" for c in resp.cookies.jar}) - return _copy_cookies +async def create_user_by_role(user: NewUser, role: UserRole) -> NewUser: + """Create a user with the specified role for further testing. + + :param user: info about user + :param role: role of the user + :return: original info about user + """ + del user.repeat_password + async with async_session() as db: + await User.create(db, user, role=role.name) + return user + + +@pytest.fixture +async def db_user(user: NewUser) -> NewUser: + """Save info about regular user into db. + + :param user: info about user + :return: info about user + """ + return await create_user_by_role(user, role=UserRole.user) + + +@pytest.fixture +async def db_moderator(user: NewUser) -> NewUser: + """Save info about moderator into db. + + :param user: info about moderator + :return: info about moderator + """ + return await create_user_by_role(user, role=UserRole.moderator) + + +@pytest.fixture +async def db_admin(user: NewUser) -> NewUser: + """Save info about admin into db. + + :param user: info about admin + :return: info about admin + """ + return await create_user_by_role(user, role=UserRole.admin) -@pytest.fixture(scope="function") +async def authorize_client(client: AsyncClient, user: NewUser) -> AsyncClient: + """Authorize a user, setting authorization cookies into the client. + + :param client: async HTTP-client + :param user: info about user + :return: async HTTP-client with authorization cookies + """ + credentials = {"username": user.username, "password": user.password} + login_resp = await client.post("/login", data=credentials) + assert login_resp.status_code == status.HTTP_200_OK + client.cookies = Cookies(dict(login_resp.cookies.items())) + return client + + +@pytest.fixture async def authorized_client( client: AsyncClient, - random_user: RandomUser, - copy_cookies: Callable + db_user: NewUser, ) -> AsyncClient: - """Async HTTP-client with authorized random user data""" - await client.post("/signup", json=random_user) - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) - client.cookies = copy_cookies(login_resp) - return client + """Async HTTP-client with authorization cookies for a regular user. + + NOTE: it's used for integration tests. + + :param client: async HTTP-client for end-to-end tests + :param created_user: info about user saved into db beforehand + :return: async HTTP-client with authorization cookies for a regular user. + """ + return await authorize_client(client, db_user) + + +@pytest.fixture +async def authorized_user_client( + e2e_client: AsyncClient, + db_user: NewUser, +) -> AsyncClient: + """Async HTTP-client with authorization cookies for a regular user. + + NOTE: it's used for end-to-end tests. + + :param client: async HTTP-client for end-to-end tests + :param created_user: info about user saved into db beforehand + :return: async HTTP-client with authorization cookies for a regular user. + """ + return await authorize_client(e2e_client, db_user) + + +@pytest.fixture +async def authorized_moderator_client( + e2e_client: AsyncClient, + db_moderator: NewUser, +) -> AsyncClient: + """Async HTTP-client with authorization cookies for a moderator. + + NOTE: it's used for end-to-end tests. + + :param client: async HTTP-client for end-to-end tests + :param db_moderator: info about moderator saved into db beforehand + :return: async HTTP-client with authorization cookies for a moderator. + """ + return await authorize_client(e2e_client, db_moderator) + + +@pytest.fixture +async def authorized_admin_client( + e2e_client: AsyncClient, + db_admin: NewUser, +) -> AsyncClient: + """Async HTTP-client with authorization cookies for an admin. + + NOTE: it's used for end-to-end tests. + + :param client: async HTTP-client for end-to-end tests + :param db_moderator: info about admin saved into db beforehand + :return: async HTTP-client with authorization cookies for an admin. + """ + return await authorize_client(e2e_client, db_admin) From 6244de95e54d7aa8e43499ab5bc333e4b07b4608 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:51:39 +0300 Subject: [PATCH 19/68] Update integration tests --- src/tests/app/user/test_routes.py | 395 +++++++++++------------------- 1 file changed, 146 insertions(+), 249 deletions(-) diff --git a/src/tests/app/user/test_routes.py b/src/tests/app/user/test_routes.py index 073654b..e55ab92 100644 --- a/src/tests/app/user/test_routes.py +++ b/src/tests/app/user/test_routes.py @@ -1,363 +1,260 @@ -import secrets -from os import getenv -from typing import Callable +from typing import TYPE_CHECKING, cast import pytest -from faker.proxy import Faker from fastapi import status -from httpx import AsyncClient +from httpx import Cookies -from tests.conftest import RandomUser +from app.main.settings import settings from app.user import exceptions as ex -from app.user.constants import CookieType as CT -from app.user.settings import MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH - - -def assert_signup_user_success(input_user: RandomUser, output_user: dict) -> None: - """ - Assertions to verify info about user after successful signup. - - :param input_user: input info about user - :param output_user: output info about user - """ - assert output_user["isActive"] is True - assert output_user["firstName"] == input_user.get("first_name") - assert output_user["lastName"] == input_user.get("last_name") - assert "password" not in output_user - assert "repeat_password" not in output_user - assert "role" not in output_user - - -@pytest.mark.anyio -async def test_signup_user_with_first_name( - faker: Faker, - client: AsyncClient, - random_user: RandomUser, -) -> None: - random_user["first_name"] = faker.name() - resp = await client.post("/signup", json=random_user) - assert resp.status_code == status.HTTP_200_OK - registered_user = resp.json() - assert_signup_user_success(random_user, registered_user) - +from app.user.constants import CookieType +from app.user.dependencies import authenticate_user -@pytest.mark.anyio -async def test_signup_user_with_last_name( - faker: Faker, - client: AsyncClient, - random_user: RandomUser, -) -> None: - random_user["last_name"] = faker.name() - resp = await client.post("/signup", json=random_user) - assert resp.status_code == status.HTTP_200_OK - registered_user = resp.json() - assert_signup_user_success(random_user, registered_user) +if TYPE_CHECKING: + from fastapi import FastAPI + from httpx import AsyncClient + from app.user.schemas import NewUser -@pytest.mark.anyio -async def test_signup_user_with_first_ane_last_name( - faker: Faker, - client: AsyncClient, - random_user: RandomUser, -) -> None: - random_user["first_name"] = faker.name() - random_user["last_name"] = faker.name() - resp = await client.post("/signup", json=random_user) - assert resp.status_code == status.HTTP_200_OK - registered_user = resp.json() - assert_signup_user_success(random_user, registered_user) - - -@pytest.mark.anyio -async def test_signup_user_without_name( - client: AsyncClient, - random_user: RandomUser -) -> None: - resp = await client.post("/signup", json=random_user) - assert resp.status_code == status.HTTP_200_OK - registered_user = resp.json() - assert_signup_user_success(random_user, registered_user) +pytestmark = pytest.mark.anyio -@pytest.mark.anyio async def test_signup_user_already_exists( - client: AsyncClient, - random_user: RandomUser + client: "AsyncClient", + user: "NewUser", ) -> None: - resp = await client.post("/signup", json=random_user) + user_dict = user.model_dump() + resp = await client.post("/signup", json=user_dict) assert resp.status_code == status.HTTP_200_OK - resp = await client.post("/signup", json=random_user) - assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert resp.json() == {"detail": ex.UserAlreadyExistsError.detail} + resp = await client.post("/signup", json=user_dict) + assert resp.status_code == status.HTTP_409_CONFLICT + assert "already exists" in resp.json()["detail"] -@pytest.mark.anyio async def test_signup_user_password_too_short( - client: AsyncClient, - random_user: RandomUser, + client: "AsyncClient", + user: "NewUser", ) -> None: - random_user["password"] = random_user["password"][:MIN_PASSWORD_LENGTH-1] - random_user["repeat_password"] = random_user["repeat_password"][:MIN_PASSWORD_LENGTH-1] - resp = await client.post("/signup", json=random_user) + user.password = user.password[: settings.min_password_length - 1] + user.repeat_password = user.password + resp = await client.post("/signup", json=user.model_dump()) assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert f"String should have at least {MIN_PASSWORD_LENGTH} characters" in resp.text + assert f"at least {settings.min_password_length} characters" in resp.text -@pytest.mark.anyio async def test_signup_user_password_too_long( - client: AsyncClient, - random_user: RandomUser, + client: "AsyncClient", + user: "NewUser", ) -> None: - random_user["password"] *= MAX_PASSWORD_LENGTH - random_user["repeat_password"] *= MIN_PASSWORD_LENGTH - resp = await client.post("/signup", json=random_user) + user.password *= settings.max_password_length + user.repeat_password = user.password + resp = await client.post("/signup", json=user.model_dump()) assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert f"String should have at most {MAX_PASSWORD_LENGTH} characters" in resp.text + assert f"at most {settings.max_password_length} characters" in resp.text -@pytest.mark.anyio async def test_signup_user_passwords_dont_match( - client: AsyncClient, - random_user: RandomUser, + client: "AsyncClient", + user: "NewUser", ) -> None: - random_user["repeat_password"] += " " - resp = await client.post("/signup", json=random_user) + user.repeat_password += " " + resp = await client.post("/signup", json=user.model_dump()) assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert resp.json() == {"detail": ex.PasswordsDontMatchError.detail} -@pytest.mark.anyio async def test_user_login_wrong_username( - faker: Faker, - client: AsyncClient, - random_user: RandomUser, + client: "AsyncClient", + user: "NewUser", ) -> None: - signup_resp = await client.post("/signup", json=random_user) + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - login_resp = await client.post("/login", data={ - "username": faker.user_name(), - "password": random_user["password"], - }) + wrong_username = user.username + " " + login_resp = await client.post( + "/login", + data={"username": wrong_username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_401_UNAUTHORIZED assert login_resp.json() == {"detail": ex.UserNotFoundError.detail} -@pytest.mark.anyio async def test_user_login_wrong_password( - faker: Faker, - client: AsyncClient, - random_user: RandomUser, + client: "AsyncClient", + user: "NewUser", ) -> None: - signup_resp = await client.post("/signup", json=random_user) + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": faker.password(), - }) + wrong_password = user.password + " " + login_resp = await client.post( + "/login", + data={"username": user.username, "password": wrong_password}, + ) assert login_resp.status_code == status.HTTP_401_UNAUTHORIZED assert login_resp.json() == {"detail": ex.IncorrectPasswordError.detail} -@pytest.mark.anyio -async def test_user_login_success( - client: AsyncClient, - random_user: RandomUser -) -> None: - signup_resp = await client.post("/signup", json=random_user) +async def test_user_login_success(client: "AsyncClient", user: "NewUser") -> None: + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_200_OK - assert login_resp.cookies.get(CT.access_token.name) - assert login_resp.cookies.get(CT.refresh_token.name) - resp_data: dict = login_resp.json() - assert resp_data.get("accessToken") - assert resp_data.get("refreshToken") + assert login_resp.cookies.get(CookieType.access_token.name) + assert login_resp.cookies.get(CookieType.refresh_token.name) + login_resp_data = login_resp.json() + assert "accessToken" in login_resp_data + assert "refreshToken" in login_resp_data -@pytest.mark.anyio -async def test_inactive_user_login( - client: AsyncClient, - random_user: RandomUser -) -> None: - from app.user.dependencies import authenticate_user +async def test_inactive_user_login(client: "AsyncClient", user: "NewUser") -> None: + async def deactivate_user() -> None: + raise ex.InactiveUserError - signup_resp = await client.post("/signup", json=random_user) + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - async def deactive_user() -> None: - raise ex.InactiveUserError - - client._transport.app.dependency_overrides[authenticate_user] = deactive_user # type: ignore - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + main_app = cast("FastAPI", client._transport.app) # type: ignore + main_app.dependency_overrides[authenticate_user] = deactivate_user + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_400_BAD_REQUEST assert login_resp.json() == {"detail": ex.InactiveUserError.detail} - del client._transport.app.dependency_overrides[authenticate_user] # type: ignore + del main_app.dependency_overrides[authenticate_user] -@pytest.mark.anyio -async def test_unauthorized_auth(client: AsyncClient) -> None: +async def test_unauthorized_auth(client: "AsyncClient") -> None: resp = await client.get("/auth") assert resp.status_code == status.HTTP_401_UNAUTHORIZED assert resp.json() == {"detail": ex.AuthenticationError.detail} -@pytest.mark.anyio -async def test_corrupted_access_token(authorized_client: AsyncClient) -> None: - access_token = CT.access_token.name - authorized_client.cookies[access_token] = f"{authorized_client.cookies[access_token]}." +async def test_corrupted_access_token(authorized_client: "AsyncClient") -> None: + access_token = CookieType.access_token.name + authorized_client.cookies[access_token] = ( + f"{authorized_client.cookies[access_token]}." + ) resp = await authorized_client.get("/auth") assert resp.status_code == status.HTTP_401_UNAUTHORIZED assert resp.json() == {"detail": ex.InvalidCredentialsError.detail} -@pytest.mark.anyio async def test_expired_access_token( monkeypatch: pytest.MonkeyPatch, - client: AsyncClient, - random_user: RandomUser, - copy_cookies: Callable, + client: "AsyncClient", + user: "NewUser", ) -> None: - """Test when the access token (but not cookie) is expired""" - signup_resp = await client.post("/signup", json=random_user) + """Test case when the access token (but not cookie) is expired.""" + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - # expire generated access token that was obtained after user login - monkeypatch.setattr("app.user.schemas.ACCESS_TOKEN_EXPIRATION_TIME", -1) - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + # expire access token (not the cookie) + monkeypatch.setattr("app.user.schemas.settings.access_token_expiration_time", -1) + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_200_OK - assert CT.access_token.name in login_resp.cookies - # copy authorization cookies into the http-client - client.cookies = copy_cookies(login_resp) + assert CookieType.access_token.name in login_resp.cookies + # copy authorization cookies into the http-client for further requests + client.cookies = Cookies(dict(login_resp.cookies.items())) auth_resp = await client.get("/auth") assert auth_resp.status_code == status.HTTP_401_UNAUTHORIZED assert auth_resp.json() == {"detail": ex.InvalidCredentialsError.detail} -@pytest.mark.anyio async def test_expired_access_token_cookie( monkeypatch: pytest.MonkeyPatch, - client: AsyncClient, - random_user: RandomUser, - copy_cookies: Callable, + client: "AsyncClient", + user: "NewUser", ) -> None: - """Test when the access token cookie is expired""" - signup_resp = await client.post("/signup", json=random_user) + """Test case when the cookie containing access token is expired.""" + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - # expire generated access token that was obtained after user login - monkeypatch.setattr("app.user.schemas.ACCESS_TOKEN_COOKIE_EXPIRATION_TIME", -1) - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + # expire cookie containing access token + monkeypatch.setattr( + "app.user.schemas.settings.access_token_cookie_expiration_time", -1 + ) + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_200_OK - assert CT.access_token.name not in login_resp.cookies - # copy authorization cookies into the http-client - client.cookies = copy_cookies(login_resp) + assert CookieType.access_token.name not in login_resp.cookies + # copy authorization cookies into the http-client for further requests + client.cookies = Cookies(dict(login_resp.cookies.items())) auth_resp = await client.get("/auth") assert auth_resp.status_code == status.HTTP_204_NO_CONTENT - assert CT.access_token.name in auth_resp.headers["set-cookie"] + assert CookieType.access_token.name in auth_resp.headers["set-cookie"] -@pytest.mark.anyio async def test_expired_refresh_token( monkeypatch: pytest.MonkeyPatch, - client: AsyncClient, - random_user: RandomUser, - copy_cookies: Callable, + client: "AsyncClient", + user: "NewUser", ) -> None: - """Test when the refresh token (but not cookie) is expired""" - signup_resp = await client.post("/signup", json=random_user) + """Test case when the refresh token (but not cookie) is expired.""" + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - # expire generated access and refresh tokens that were obtained after user login - monkeypatch.setattr("app.user.schemas.ACCESS_TOKEN_EXPIRATION_TIME", -1) - monkeypatch.setattr("app.user.schemas.REFRESH_TOKEN_EXPIRATION_TIME", -1) - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + # expire both access and refresh tokens (not the cookies) + monkeypatch.setattr("app.user.schemas.settings.access_token_expiration_time", -1) + monkeypatch.setattr("app.user.schemas.settings.refresh_token_expiration_time", -1) + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_200_OK - assert CT.access_token.name in login_resp.cookies - assert CT.refresh_token.name in login_resp.cookies - # copy authorization cookies into the http-client - client.cookies = copy_cookies(login_resp) + assert CookieType.access_token.name in login_resp.cookies + assert CookieType.refresh_token.name in login_resp.cookies + # copy authorization cookies into the http-client for further requests + client.cookies = Cookies(dict(login_resp.cookies.items())) auth_resp = await client.get("/auth") assert auth_resp.status_code == status.HTTP_401_UNAUTHORIZED assert auth_resp.json() == {"detail": ex.InvalidCredentialsError.detail} -@pytest.mark.anyio async def test_expired_refresh_token_and_cookie( monkeypatch: pytest.MonkeyPatch, - client: AsyncClient, - random_user: RandomUser, - copy_cookies: Callable, + client: "AsyncClient", + user: "NewUser", ) -> None: - """Test when the access token and refresh token cookies are expired""" - signup_resp = await client.post("/signup", json=random_user) + """Test case when the cookies containing access and refresh tokens are expired.""" + signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - # expire generated access token that was obtained after user login - monkeypatch.setattr("app.user.schemas.ACCESS_TOKEN_COOKIE_EXPIRATION_TIME", -1) - monkeypatch.setattr("app.user.schemas.REFRESH_TOKEN_COOKIE_EXPIRATION_TIME", -1) - login_resp = await client.post("/login", data={ - "username": random_user["username"], - "password": random_user["password"], - }) + + # expire cookies containing access and refresh tokens + monkeypatch.setattr( + "app.user.schemas.settings.access_token_cookie_expiration_time", -1 + ) + monkeypatch.setattr( + "app.user.schemas.settings.refresh_token_cookie_expiration_time", -1 + ) + login_resp = await client.post( + "/login", + data={"username": user.username, "password": user.password}, + ) assert login_resp.status_code == status.HTTP_200_OK - assert CT.access_token.name not in login_resp.cookies - # copy authorization cookies into the http-client - client.cookies = copy_cookies(login_resp) + assert CookieType.access_token.name not in login_resp.cookies + # copy authorization cookies into the http-client for further requests + client.cookies = Cookies(dict(login_resp.cookies.items())) auth_resp = await client.get("/auth") assert auth_resp.status_code == status.HTTP_401_UNAUTHORIZED assert auth_resp.json() == {"detail": ex.AuthenticationError.detail} -@pytest.mark.anyio -async def test_logout_user(authorized_client: AsyncClient) -> None: +async def test_logout_user(authorized_client: "AsyncClient") -> None: resp = await authorized_client.post("/logout") assert resp.status_code == status.HTTP_205_RESET_CONTENT - assert f'{CT.access_token.name}=""' in resp.headers['set-cookie'] - assert f'{CT.refresh_token.name}=""' in resp.headers['set-cookie'] + assert f'{CookieType.access_token.name}=""' in resp.headers["set-cookie"] + assert f'{CookieType.refresh_token.name}=""' in resp.headers["set-cookie"] + assert not resp.cookies -@pytest.mark.anyio -async def test_reuse_access_token_cookie_after_logout( - authorized_client: AsyncClient -) -> None: +async def test_reuse_cookies_after_logout(authorized_client: "AsyncClient") -> None: + """Test case when a user reuses cookies with tokens after logout.""" logout_resp = await authorized_client.post("/logout") assert logout_resp.status_code == status.HTTP_205_RESET_CONTENT - authorized_client.cookies.set( - name=CT.access_token.name, - value=authorized_client.cookies.get(CT.access_token.name) or "", - ) auth_resp = await authorized_client.get("/auth") assert auth_resp.status_code == status.HTTP_401_UNAUTHORIZED assert auth_resp.json() == {"detail": ex.ExpiredTokenError.detail} - - -@pytest.mark.anyio -async def test_reuse_refresh_token_cookie_after_logout( - authorized_client: AsyncClient -) -> None: - logout_resp = await authorized_client.post("/logout") - assert logout_resp.status_code == status.HTTP_205_RESET_CONTENT - authorized_client.cookies.set( - name=CT.refresh_token.name, - value=authorized_client.cookies.get(CT.refresh_token.name) or "", - ) - auth_resp = await authorized_client.get("/auth") - assert auth_resp.status_code == status.HTTP_401_UNAUTHORIZED - assert auth_resp.json() == {"detail": ex.ExpiredTokenError.detail} - - -# @pytest.mark.skip(reason="Not Implemented") -# @pytest.mark.anyio -# async def test_user_access_to_admin_resources(authorized_client: AsyncClient) -> None: -# pass From 65bb8e49cf4690e861706b352c81232f2f1e37f6 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:52:07 +0300 Subject: [PATCH 20/68] Add healthchecks. Update service dependencies --- docker-compose.yml | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c9abb2f..a3f2c5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,8 @@ -version: '3.8' - services: - auth: + app: build: . - container_name: auth - restart: on-failure + container_name: app + restart: always environment: - APP_HOST=${APP_HOST} - APP_PORT=${APP_PORT} @@ -15,30 +13,47 @@ services: - JWT_SECRET_KEY=${JWT_SECRET_KEY} - REDIS_URL=${REDIS_URL} - SERVER_DOMAIN=${SERVER_DOMAIN} - volumes: - - ./src:/app depends_on: - - db - - redis + db: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - static_data:/src/.venv/lib/python3.12/site-packages/sqladmin/statics + healthcheck: + test: ["CMD-SHELL", "curl -f http://${APP_HOST}:${APP_PORT}/health || exit 1"] + interval: 5s + timeout: 5s + retries: 5 db: image: postgres:16.3-alpine container_name: db - restart: on-failure + restart: always environment: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 5s + timeout: 10s + retries: 5 redis: image: redis:7.2-alpine container_name: redis - restart: on-failure + restart: always command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data + healthcheck: + test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD} ping"] + interval: 5s + timeout: 10s + retries: 5 nginx: build: @@ -51,12 +66,15 @@ services: - NGINX_PORT=${NGINX_PORT} - NGINX_SERVER_HOST=${NGINX_SERVER_HOST} depends_on: - - auth + app: + condition: service_healthy ports: - ${NGINX_PORT}:${NGINX_PORT} volumes: - ./proxy/snippets:/etc/nginx/snippets + - static_data:/api/statics volumes: pg_data: - redis_data: \ No newline at end of file + redis_data: + static_data: From 8844bb4b7f910d6a2652a799af6498af549870cd Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:52:36 +0300 Subject: [PATCH 21/68] Remove unique index from primary key attribute --- src/alembic/versions/986ea1a0e2e2_init_commit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/alembic/versions/986ea1a0e2e2_init_commit.py b/src/alembic/versions/986ea1a0e2e2_init_commit.py index 4556069..c4c1469 100644 --- a/src/alembic/versions/986ea1a0e2e2_init_commit.py +++ b/src/alembic/versions/986ea1a0e2e2_init_commit.py @@ -46,7 +46,6 @@ def upgrade() -> None: sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True) - op.create_index(op.f("ix_users_id"), "users", ["id"], unique=True) op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True) # ### end Alembic commands ### @@ -54,7 +53,6 @@ def upgrade() -> None: def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f("ix_users_username"), table_name="users") - op.drop_index(op.f("ix_users_id"), table_name="users") op.drop_index(op.f("ix_users_email"), table_name="users") op.drop_table("users") # ### end Alembic commands ### From fd2039da902d77f93b741f0688ef2df0a0ac0ea2 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:54:21 +0300 Subject: [PATCH 22/68] Remove unused files --- src/app/admin/settings.py | 4 ---- src/app/main/redis.py | 30 ------------------------------ src/app/user/settings.py | 17 ----------------- 3 files changed, 51 deletions(-) delete mode 100644 src/app/admin/settings.py delete mode 100644 src/app/main/redis.py delete mode 100644 src/app/user/settings.py diff --git a/src/app/admin/settings.py b/src/app/admin/settings.py deleted file mode 100644 index 589edf9..0000000 --- a/src/app/admin/settings.py +++ /dev/null @@ -1,4 +0,0 @@ -from os import getenv - - -ADMIN_DEBUG: bool= bool(getenv("ADMIN_DEBUG", True)) diff --git a/src/app/main/redis.py b/src/app/main/redis.py deleted file mode 100644 index 0a36006..0000000 --- a/src/app/main/redis.py +++ /dev/null @@ -1,30 +0,0 @@ -from redis.asyncio import ConnectionPool, Redis - - -def create_pool(url: str) -> ConnectionPool: - """ - Creates a connection pool for Redis. - - :return: connection pool for Redis - """ - return ConnectionPool.from_url(url, decode_responses=True) - - -def create_client(pool: ConnectionPool) -> Redis: - """ - Create a Redis client object using connection pool. - - :param pool: connection pool for Redis - :return: client for Redis - """ - return Redis(connection_pool=pool) - - -async def dispose_client(client: Redis) -> None: - """ - Close the connection pool and client for Redis. - - :param client: client for Redis - """ - await client.close() - await client.connection_pool.disconnect() diff --git a/src/app/user/settings.py b/src/app/user/settings.py deleted file mode 100644 index 4e671a0..0000000 --- a/src/app/user/settings.py +++ /dev/null @@ -1,17 +0,0 @@ -from os import getenv -from pathlib import Path - - -JWT_ALGORITHM: str = getenv("JWT_ALGORITHM", "HS256") -JWT_SECRET_KEY: str = getenv("JWT_SECRET_KEY", "secret123") - -ACCESS_TOKEN_EXPIRATION_TIME: int = 5 # minutes -ACCESS_TOKEN_COOKIE_EXPIRATION_TIME = ACCESS_TOKEN_EXPIRATION_TIME * 60 # seconds -REFRESH_TOKEN_EXPIRATION_TIME: int = 20 # minutes -REFRESH_TOKEN_COOKIE_EXPIRATION_TIME = REFRESH_TOKEN_EXPIRATION_TIME * 60 # seconds -TOKEN_SCHEME: str = "Bearer" - -MAX_PASSWORD_LENGTH: int = 50 -MIN_PASSWORD_LENGTH: int = 5 - -USER_POLICY_FILE: Path = Path(__file__).parents[2] / "policy.json" From 61d4e8e06f8f08ec37e3d7a6915cbf9e17e2463f Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:54:53 +0300 Subject: [PATCH 23/68] Create custom base exception class --- src/app/main/exceptions.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/app/main/exceptions.py diff --git a/src/app/main/exceptions.py b/src/app/main/exceptions.py new file mode 100644 index 0000000..9b3d213 --- /dev/null +++ b/src/app/main/exceptions.py @@ -0,0 +1,23 @@ +from fastapi import HTTPException, status + +from app.main.settings import settings + + +class BaseHTTPException(HTTPException): + """Base HTTP exception class.""" + + detail: str + status_code: int = status.HTTP_401_UNAUTHORIZED + + def __init__( + self, + status_code: int | None = None, + detail: str | None = None, + headers: dict[str, str] | None = None, + ) -> None: + """Set status code, details and headers for the exception.""" + super().__init__( + status_code or self.status_code, + detail or self.detail, + headers or {"WWW-Authenticate": settings.token_scheme}, + ) From b64d09b9d523bb9ee664f061e83bdb66d94bc90a Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:55:17 +0300 Subject: [PATCH 24/68] Add configuration for logger --- src/app/main/logger.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/app/main/logger.py diff --git a/src/app/main/logger.py b/src/app/main/logger.py new file mode 100644 index 0000000..9c51d94 --- /dev/null +++ b/src/app/main/logger.py @@ -0,0 +1,13 @@ +import logging +from typing import TYPE_CHECKING + +from .settings import settings + +if TYPE_CHECKING: + from logging import Logger + + +def configure_logging() -> "Logger": + """Configure app logging and return logger object.""" + logging.basicConfig(**settings.logging_kwargs) + return logging.getLogger(settings.log_name) From 8f51aed784d5282a8029c6ea09287e1a8266c9f9 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:56:06 +0300 Subject: [PATCH 25/68] Set separate settings for FastAPI and Admin apps, logging and CORS --- src/app/main/settings.py | 156 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 6 deletions(-) diff --git a/src/app/main/settings.py b/src/app/main/settings.py index 4027b64..deebd1f 100644 --- a/src/app/main/settings.py +++ b/src/app/main/settings.py @@ -1,8 +1,152 @@ -from os import getenv +import logging +from functools import lru_cache +from pathlib import Path +from typing import TypedDict +from pydantic_settings import BaseSettings -CORS_MAX_AGE: int = 600 # seconds -DATABASE_URL: str = getenv("DATABASE_URL", "sqlite+aiosqlite:///:memory:") -DEBUG: bool = bool(int(getenv("DEBUG", 0))) -REDIS_URL: str = getenv("REDIS_URL", "redis://localhost:6379") -SERVER_DOMAIN: str = getenv("SERVER_DOMAIN", "example.com") + +class FastAPIKwargs(TypedDict): + """Kwargs for FastAPI app.""" + + title: str + description: str + version: str + debug: bool + + +class LoggingKwargs(TypedDict): + """Kwargs for logger config.""" + + level: int + format: str + datefmt: str + + +class AdminAppKwargs(TypedDict): + """Kwargs for Admin app.""" + + base_url: str + title: str + debug: bool + + +class CORSMiddlewareKwargs(TypedDict): + """Kwargs for CORSMiddleware config.""" + + allow_origins: list[str] + allow_credentials: bool + allow_methods: list[str] + allow_headers: list[str] + max_age: int + + +class Settings(BaseSettings): + """Main project settings.""" + + # FastAPI settings + title: str = "JWT Auth API Gateway" + description: str = "RBAC JWT Cookies-based API Gateway" + version: str = "0.0.1" + debug: bool = True + + # Admin app setting + admin_base_url: str = "/admin" + admin_title: str = "Auth API Admin" + admin_debug: bool = True + + # Logging settings + log_name: str = "app" + log_level: int = logging.INFO + log_format: str = "%(levelname)s - %(name)s - %(asctime)s - %(message)s" + log_datefmt: str = "%Y-%m-%d %H:%M:%S" + # name of the logger used in testing + test_log_name: str = "test" + + host_server_domain: str = "localhost" + + # CORS settings + cors_max_age: int = 600 # seconds + cors_allow_origins: list[str] = [host_server_domain] + cors_allow_credentials: bool = True + cors_allow_methods: list[str] = ["*"] + cors_allow_headers: list[str] = ["*"] + + # Database settings + database_url: str = "sqlite+aiosqlite:///./main.db" + test_database_url: str = "sqlite+aiosqlite:///:memory:" + + # Redis settings + redis_url: str = "redis://localhost:6379/0" + + # JWT settings + jwt_algorithm: str = "HS256" + jwt_secret_key: str = "supersecret12345" # noqa: S105 + + # JWT Tokens settings + access_token_expiration_time: int = 5 # minutes + access_token_cookie_expiration_time: int = ( + access_token_expiration_time * 60 + ) # seconds + refresh_token_expiration_time: int = 20 # minutes + refresh_token_cookie_expiration_time: int = ( + refresh_token_expiration_time * 60 + ) # seconds + token_scheme: str = "Bearer" # noqa: S105 + # size of the cache to store payload of the corresponding tokens + token_payload_max_cache_hits: int = 10_000 + + # Passwords settings + max_password_length: int = 50 + min_password_length: int = 5 + + # RBAC settings + user_policy_file: Path = Path(__file__).parents[2] / "policy.json" + + @property + def fastapi_kwargs(self) -> FastAPIKwargs: + """Kwargs for FastAPI app.""" + return FastAPIKwargs( + title=self.title, + description=self.description, + version=self.version, + debug=self.debug, + ) + + @property + def logging_kwargs(self) -> LoggingKwargs: + """Kwargs for logger config.""" + return LoggingKwargs( + level=settings.log_level, + format=self.log_format, + datefmt=self.log_datefmt, + ) + + @property + def admin_app_kwargs(self) -> AdminAppKwargs: + """Kwargs for Admin app.""" + return AdminAppKwargs( + base_url=self.admin_base_url, + title=self.admin_title, + debug=self.admin_debug, + ) + + @property + def cors_kwargs(self) -> CORSMiddlewareKwargs: + """Kwargs for CORS middleware.""" + return CORSMiddlewareKwargs( + allow_origins=self.cors_allow_origins, + allow_credentials=self.cors_allow_credentials, + allow_methods=self.cors_allow_methods, + allow_headers=self.cors_allow_headers, + max_age=self.cors_max_age, + ) + + +@lru_cache +def get_settings() -> Settings: + """Return cached project settings.""" + return Settings() + + +settings = get_settings() From 1f8b8664a15b5b0b3b826698942466269a30a280 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:56:50 +0300 Subject: [PATCH 26/68] Update declaration of the db engine and base declarative class --- src/app/main/db.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/main/db.py b/src/app/main/db.py index 417c0ae..05053f9 100644 --- a/src/app/main/db.py +++ b/src/app/main/db.py @@ -1,24 +1,25 @@ -from datetime import datetime -from typing import ClassVar, TypeVar +from typing import ClassVar -from sqlalchemy import DateTime, String, MetaData -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy import MetaData +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) from sqlalchemy.orm import DeclarativeBase -from .settings import DEBUG, DATABASE_URL +from .settings import settings - -DBType = TypeVar("DBType", bound=AsyncSession) - -engine = create_async_engine(DATABASE_URL, echo=DEBUG) -async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) +engine = create_async_engine(settings.database_url, echo=settings.debug) +async_session = async_sessionmaker( + engine, + class_=AsyncSession, + expire_on_commit=False, +) metadata = MetaData() class Base(DeclarativeBase): - """Base declarative class""" + """Base declarative class.""" + metadata: ClassVar[MetaData] = metadata - type_annotation_map: ClassVar[dict] = { - datetime: DateTime(timezone=True), - str: String(255), - } From 7ad062ad3d156dd178cbcb49a832ddf36adf0dfb Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:57:18 +0300 Subject: [PATCH 27/68] Add type aliases for db and redis dependencies --- src/app/main/dependencies.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/app/main/dependencies.py b/src/app/main/dependencies.py index 59b1991..7dbaba4 100644 --- a/src/app/main/dependencies.py +++ b/src/app/main/dependencies.py @@ -1,16 +1,17 @@ -from typing import AsyncIterator +from collections.abc import AsyncIterator +from typing import TYPE_CHECKING, Annotated, TypeAlias, cast -from fastapi import Request -from redis.asyncio import Redis -from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import Depends, Request from .db import async_session +if TYPE_CHECKING: + from redis.asyncio import Redis + from sqlalchemy.ext.asyncio import AsyncSession -async def get_db() -> AsyncIterator[AsyncSession]: - """ - Yield a database session to perform operatrions - with the database + +async def get_db() -> AsyncIterator["AsyncSession"]: + """Yield a database session to be used as a dependency. :yield: database session """ @@ -18,12 +19,14 @@ async def get_db() -> AsyncIterator[AsyncSession]: yield session -async def get_redis(request: Request) -> Redis: - """ - Return initialized on the startup of the app - client for Redis. +async def get_redis(request: Request) -> "Redis": + """Return initialized Redis client to be used as a dependency. - :param request: incoming request - :return: client for Redis + :param request: request object providing access to the app state + :return: Redis client """ - return request.app.state.redis + return cast("Redis", request.app.state.redis) + + +DbSession: TypeAlias = Annotated["AsyncSession", Depends(get_db)] +RedisT: TypeAlias = Annotated["Redis", Depends(get_redis)] From 1479e129ba62cac926bc71cd13b7d42d80afbb6b Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:57:54 +0300 Subject: [PATCH 28/68] Update lifespan events and configuration of CORS --- src/app/main/app.py | 64 +++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/app/main/app.py b/src/app/main/app.py index fd10bf6..554ebce 100644 --- a/src/app/main/app.py +++ b/src/app/main/app.py @@ -1,50 +1,58 @@ +from collections.abc import AsyncIterator from contextlib import asynccontextmanager -from typing import AsyncIterator from fastapi import FastAPI +from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware +from redis.asyncio import ConnectionPool, Redis +from sqlalchemy.exc import IntegrityError -from .db import engine -from .routes import router as default_router -from .settings import DEBUG, CORS_MAX_AGE, REDIS_URL, SERVER_DOMAIN from app.admin.app import init_admin_app +from app.user.errors import db_integrity_error_handler, validation_error_handler from app.user.routes import router as user_router +from .db import engine +from .logger import configure_logging +from .routes import router as default_router +from .settings import settings + @asynccontextmanager -async def lifespan(app: FastAPI) -> AsyncIterator: - """ - On app startup: - - initialize the connection pool and client for Redis; - - add the client for Redis to the state object of the app. +async def lifespan(_: FastAPI) -> AsyncIterator[None]: + """Init and release objects on app startup and shutdown. - On app shutdown: - - dispose the connection pool and client for Redis; - - disponse the database engine. + On startup: + - init Redis client with connection pool; - :param app: app object + On shutdown: + - dispose Redis client with connection pool; + - dispose database engine. """ - from .redis import create_client, create_pool, dispose_client - - redis_pool = create_pool(REDIS_URL) - redis_client = create_client(redis_pool) - setattr(app.state, "redis", redis_client) + redis_pool = ConnectionPool.from_url( + settings.redis_url, + decode_responses=True, + ) + redis_client = Redis(connection_pool=redis_pool) + + # set application state + app.state.logger = configure_logging() + app.state.redis = redis_client yield - await dispose_client(redis_client) + await redis_client.close() + await redis_client.connection_pool.disconnect() await engine.dispose() -app = FastAPI(debug=DEBUG, lifespan=lifespan) -app.add_middleware( - CORSMiddleware, - allow_origins=[SERVER_DOMAIN], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - max_age=CORS_MAX_AGE, +app = FastAPI( + **settings.fastapi_kwargs, + lifespan=lifespan, + exception_handlers={ + IntegrityError: db_integrity_error_handler, + RequestValidationError: validation_error_handler, + }, ) +app.add_middleware(CORSMiddleware, **settings.cors_kwargs) app.include_router(default_router) app.include_router(user_router) -# Initialize Admin app init_admin_app(app, engine) From 1a7f54dfcd82ca733d23dc9aa2c355b80631616e Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:58:19 +0300 Subject: [PATCH 29/68] Update healthcheck endpoint --- src/app/main/routes.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/main/routes.py b/src/app/main/routes.py index 857cbcb..1f4145e 100644 --- a/src/app/main/routes.py +++ b/src/app/main/routes.py @@ -1,10 +1,12 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Response, status +router = APIRouter() -router = APIRouter(tags=["default"]) - -@router.get("/health") -def health() -> dict[str, str]: - """Healthcheck endpoint""" - return {"status": "OK"} +@router.get("/health", status_code=status.HTTP_204_NO_CONTENT) +async def health() -> Response: + """Health-check endpoint.""" + return Response( + status_code=status.HTTP_204_NO_CONTENT, + headers={"x-status": "health"}, + ) From 3e8d465ebfb867d9337207acf33cd8562a1db62c Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:58:55 +0300 Subject: [PATCH 30/68] Add error handlers for database and data validation errors --- src/app/user/errors.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/app/user/errors.py diff --git a/src/app/user/errors.py b/src/app/user/errors.py new file mode 100644 index 0000000..722b81a --- /dev/null +++ b/src/app/user/errors.py @@ -0,0 +1,32 @@ +from typing import TYPE_CHECKING, cast + +from fastapi import Request, status +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from sqlalchemy.exc import IntegrityError + +if TYPE_CHECKING: + from logging import Logger + + +async def db_integrity_error_handler( + request: Request, + e: IntegrityError, +) -> JSONResponse: + """Database. Integrity error handler.""" + logger = cast("Logger", request.app.state.logger) + logger.warning("Database Integrity Error: %s", e) + client_message = {"detail": "User already exists"} + return JSONResponse(client_message, status.HTTP_409_CONFLICT) + + +async def validation_error_handler( + _: Request, + e: RequestValidationError, +) -> JSONResponse: + """Pydantic validation error handler.""" + error = e.errors()[0] + field = error["loc"][1] + error_message = f"{error['msg']}. Field: {field}" + client_message = {"detail": error_message} + return JSONResponse(client_message, status.HTTP_422_UNPROCESSABLE_ENTITY) From 9040d7d89fefd3b6a7bc93aa3686bd56869f0c42 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:59:29 +0300 Subject: [PATCH 31/68] Move middleware to handle OAuth flow --- src/app/user/middlewares.py | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/app/user/middlewares.py diff --git a/src/app/user/middlewares.py b/src/app/user/middlewares.py new file mode 100644 index 0000000..e5e29fe --- /dev/null +++ b/src/app/user/middlewares.py @@ -0,0 +1,54 @@ +from fastapi.openapi.models import OAuthFlowPassword, OAuthFlows +from fastapi.security import OAuth2 +from fastapi.security.utils import get_authorization_scheme_param +from starlette.requests import Request + +from app.main.settings import settings + +from .exceptions import AuthenticationError + + +class OAuth2PasswordBearerWithCookie(OAuth2): + """OAuth2 flow for authentication based on cookies.""" + + def __init__( + self, + tokenUrl: str, # noqa: N803 + scheme_name: str | None = None, + scopes: dict[str, str] | None = None, + auto_error: bool = True, + ) -> None: + """Init OAuth flow.""" + flows = OAuthFlows( + password=OAuthFlowPassword(tokenUrl=tokenUrl, scopes=scopes or {}) + ) + super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error) + + async def __call__(self, request: Request) -> str | None: + """Validate JWT token taken from the access or refresh token. + + If access token is missing (e.g. cookie is expired), then + refresh token will be validated. + + if both tokens are missing, 'AuthenticationError' will be raised + (user must re-login). + + :param request: request object providing access to cookies + :raises AuthenticationError: if tokens are invalid or corrupted + :return: access or refresh token + """ + # Check if access token is valid + access_token_cookie = request.cookies.get("access_token") + access_token_scheme, access_token = get_authorization_scheme_param( + access_token_cookie + ) + if access_token or access_token_scheme == settings.token_scheme: + return access_token + # Check if refresh token is valid + refresh_token_cookie = request.cookies.get("refresh_token") + refresh_token_scheme, refresh_token = get_authorization_scheme_param( + refresh_token_cookie + ) + if refresh_token or refresh_token_scheme == settings.token_scheme: + return refresh_token + raise AuthenticationError From e21ab41167096bdb2ae9fbaa3b6fbefa5125a536 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 20:59:58 +0300 Subject: [PATCH 32/68] Update User model attributes and method to create a new user --- src/app/user/models.py | 90 ++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/src/app/user/models.py b/src/app/user/models.py index 5187f9e..bb68d68 100644 --- a/src/app/user/models.py +++ b/src/app/user/models.py @@ -1,64 +1,88 @@ from datetime import datetime +from typing import TYPE_CHECKING -from sqlalchemy import Boolean, func, select, sql -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapped_column, Mapped +from sqlalchemy import Boolean, DateTime, func, select +from sqlalchemy.orm import Mapped, mapped_column -from .constants import UserRole as UR -from .exceptions import UserAlreadyExistsError, UserNotFoundError -from .schemas import NewUser, UserInDB -from app.main.db import Base, DBType +from app.main.db import Base + +from .auth import generate_password_hash +from .constants import UserRole +from .exceptions import UserNotFoundError +from .schemas import UserInDB + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + + from .schemas import NewUser class User(Base): + """Model to represent a user in the database.""" + __tablename__ = "users" - id: Mapped[int] = mapped_column(primary_key=True, index=True, unique=True) + id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(index=True, unique=True) email: Mapped[str] = mapped_column(index=True, unique=True) - first_name: Mapped[str | None] - last_name: Mapped[str | None] - hashed_password: Mapped[str] - role: Mapped[str] = mapped_column(default=UR.user.name, nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean, server_default=sql.expression.true()) - created_at: Mapped[datetime] = mapped_column(server_default=func.now()) - updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now()) + first_name: Mapped[str] + last_name: Mapped[str] + password: Mapped[str] + role: Mapped[str] = mapped_column(default=UserRole.user.name, nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean, default=True) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + ) @classmethod - async def get(cls, db: DBType, username: str) -> UserInDB: - """ - Return info about user. + async def get(cls, db: "AsyncSession", username: str) -> UserInDB: + """Return info about user. :param db: DB session :param username: username of the user :raises UserNotFoundError: if the user doesn't exist in the database :return: info about user """ - users_table_cols = ( - cls.first_name, cls.last_name, cls.username, - cls.email, cls.hashed_password, cls.is_active, + cols = ( + cls.first_name, + cls.last_name, + cls.username, + cls.email, + cls.password, + cls.is_active, cls.role, ) - query = select(*users_table_cols).where(cls.username == username) + query = select(*cols).where(cls.username == username) if user := (await db.execute(query)).one_or_none(): - return UserInDB.model_construct(**user._asdict()) + return UserInDB.model_construct(**user._asdict()) # pyright: ignore[reportPrivateUsage] raise UserNotFoundError - @classmethod - async def create(cls, db: DBType, user: NewUser, **kwargs: str) -> NewUser: - """ - Save info about new user into the database. + async def create( + cls, + db: "AsyncSession", + user: "NewUser", + **kwargs: str, + ) -> "NewUser": + """Save info about new user into the database. :param db: DB session :param user: info about new user :raises UserAlreadyExistsError: if such user already exists :return: info about user from the database """ - db.add(cls(**user.model_dump(), **kwargs)) - try: - await db.commit() - except IntegrityError: - await db.rollback() - raise UserAlreadyExistsError + db.add( + cls( + **user.model_dump(exclude={"password"}), + password=generate_password_hash(user.password), + **kwargs, + ) + ) + await db.commit() return user From 8a1059b98dd043291f333ac7ce879dab3522fb20 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:00:37 +0300 Subject: [PATCH 33/68] Update validators. Add description to schema attributes --- src/app/user/schemas.py | 210 +++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 100 deletions(-) diff --git a/src/app/user/schemas.py b/src/app/user/schemas.py index bb5d62b..38942de 100644 --- a/src/app/user/schemas.py +++ b/src/app/user/schemas.py @@ -1,151 +1,161 @@ -from datetime import datetime as dt, timedelta, timezone as tz -from uuid import UUID, uuid4 +from datetime import UTC, datetime, timedelta +from typing import Self +from uuid import uuid4 from pydantic import ( BaseModel, ConfigDict, EmailStr, Field, - field_validator, ValidationInfo, + field_validator, + model_validator, ) +from pydantic.alias_generators import to_camel -from .constants import CookieType as CT, TokenType as TT -from .exceptions import PasswordsDontMatchError -from .settings import ( - ACCESS_TOKEN_COOKIE_EXPIRATION_TIME, - ACCESS_TOKEN_EXPIRATION_TIME, - MAX_PASSWORD_LENGTH, - MIN_PASSWORD_LENGTH, - REFRESH_TOKEN_COOKIE_EXPIRATION_TIME, - REFRESH_TOKEN_EXPIRATION_TIME, -) -from app.main.settings import SERVER_DOMAIN - - -def snake_to_camel(s: str) -> str: - """ - Convert the given string from snake_case into camelCase notation. +from app.main.settings import settings - :param s: string to convert - :return: string in camelCase - """ - first, *other = s.split("_") - return "".join((first, *map(str.title, other))) +from .constants import CookieType, TokenType +from .exceptions import PasswordsDontMatchError class BaseCustomModel(BaseModel): - """Base custom pydantic model""" - model_config = ConfigDict( - alias_generator = snake_to_camel, - populate_by_name=True, - ) + """Base custom pydantic model.""" + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) class Token(BaseCustomModel): - """Schema to provide info about access and refresh JWT tokens""" - access_token: str - refresh_token: str + """Schema to provide info about access and refresh JWT tokens.""" + + access_token: str = Field(description="JWT access token") + refresh_token: str = Field(description="JWT refresh token") class TokenPayload(BaseModel): - """Schema to provide info about payload of a JWT token""" - sub: str - typ: TT - role: str - jti: UUID | str = Field(default_factory=uuid4, validate_default=True) - exp: dt = Field(default_factory=lambda: dt.now(tz.utc), validate_default=True) - - _jti_to_str = field_validator("jti")(lambda jti: str(jti)) + """Schema to provide info about payload of a JWT token.""" + + sub: str = Field(description="Subject of the token (user's username)") + typ: TokenType = Field(description="Type of the token (access or refresh)") + role: str = Field(description="Role of the user (admin, user, etc.)") + jti: str = Field( + default_factory=lambda: str(uuid4()), + description="JWT ID (unique identifier of the token)", + ) + exp: datetime = Field( + default_factory=lambda: datetime.now(UTC), + validate_default=True, + description="Expiration date and time of the token", + ) @field_validator("exp") @classmethod - def set_exp(cls, dt_now: dt, info: ValidationInfo) -> dt: - """ - Set expiration date of the token based on its type + def set_exp(cls, now: datetime, info: ValidationInfo) -> datetime: + """Set expiration date of the token based on its type. - :param dt_now: current value of 'exp' attribute - :param info: dict of values of the schema - :return: new value of 'exp' attribute + :param now: value of the attribute + :param info: all schema values + :return: expiration date of the token based on its type """ - exp = ACCESS_TOKEN_EXPIRATION_TIME if info.data["typ"] == TT.access \ - else REFRESH_TOKEN_EXPIRATION_TIME - return dt_now + timedelta(minutes=exp) + exp = ( + settings.access_token_expiration_time + if info.data["typ"] == TokenType.access + else settings.refresh_token_expiration_time + ) + return now + timedelta(minutes=exp) class UserCookie(BaseModel): - """Schema to set a cookie to store a JWT token""" - key: CT | str - value: str - domain: str = SERVER_DOMAIN - expires: int | None = Field(default=None, validate_default=True) - max_age: int | None = Field(default=None, validate_default=True) - httponly: bool = True - secure: bool = True + """Schema to set a cookie to store a JWT token.""" + + key: CookieType | str = Field(description="Key (name) of the cookie") + value: str = Field(description="Value of the cookie (JWT token)") + domain: str = Field( + default=settings.host_server_domain, + description="Domain of the server the cookie is associated with", + ) + expires: int | None = Field( + default=None, + validate_default=True, + description="Expiration time of the cookie (in seconds)", + ) + max_age: int | None = Field( + default=None, + validate_default=True, + description="Maximum age of the cookie (in seconds)", + ) + httponly: bool = Field(default=True, description="Whether the cookie is HTTP-only") + secure: bool = Field(default=True, description="Whether the cookie is secure") + + @field_validator("key") + @classmethod + def key_to_str(cls, key: CookieType) -> str: + """Convert key from Enum to str. - _key_to_str = field_validator("key")(lambda k: getattr(k, "name")) + :param key: enum key + :return: name attribute of the enum + """ + return key.name @field_validator("expires", "max_age", mode="before") @classmethod def set_cookie_expiration_time(cls, _: None, info: ValidationInfo) -> int: - """ - Set expiration time of the cookie (in seconds) based on its type + """Set expiration time of the cookie (in seconds) based on its type. - :param _: current value of the attribute - :param info: dict of values of the schema - :return: new value of the attribute + :param _: value of the attribute + :param info: all schema values + :return: expiration time of the cookie based on its type """ - return ACCESS_TOKEN_COOKIE_EXPIRATION_TIME if info.data["key"] == CT.access_token else \ - REFRESH_TOKEN_COOKIE_EXPIRATION_TIME + return ( + settings.access_token_cookie_expiration_time + if info.data["key"] == CookieType.access_token + else settings.refresh_token_cookie_expiration_time + ) class BaseUser(BaseCustomModel): - """Schema to provide base info about user""" - username: str - email: EmailStr - first_name: str | None = None - last_name: str | None = None + """Schema to provide base info about user.""" + username: str = Field(description="Unique username of the user") + email: EmailStr = Field(description="Email address of the user") + first_name: str = Field(description="First name of the user") + last_name: str = Field(description="Last name of the user") -class User(BaseUser): - """Schema to represent public info about user""" - is_active: bool = True +class User(BaseUser): + """Schema to represent public info about user.""" -class NewUser(BaseUser): - """Schema with the information about new user""" - password: str = Field( - min_length=MIN_PASSWORD_LENGTH, - max_length=MAX_PASSWORD_LENGTH, + is_active: bool = Field( + default=True, + description="Whether the user is active. Inactive users can't log in", ) - repeat_password: str = Field( - min_length=MIN_PASSWORD_LENGTH, - max_length=MAX_PASSWORD_LENGTH, - ) - _hashed_password: str - def __init__(self, **data: str) -> None: - """ - Verify if the original and the repeat password match and - generate a hashed password (private attribute of the model) - based on the original password. - Once the hashed password is generated, the original and the repeat - password are delete from the model. +# Custom field to specify a password +PasswordField = Field( + min_length=settings.min_password_length, + max_length=settings.max_password_length, + description="Password of the user", +) - :raises PasswordsDontMatchError: if the passwords don't match - """ - from .auth import generate_password_hash - super().__init__(**data) - if data["password"] != data["repeat_password"]: +class NewUser(BaseUser): + """Schema with the information about new user.""" + + password: str = PasswordField + repeat_password: str = PasswordField + + @model_validator(mode="after") + def check_passwords_match(self) -> Self: + """Check if the original and repeated passwords match.""" + if self.password != self.repeat_password: raise PasswordsDontMatchError - self._hashed_password = generate_password_hash(data["password"]) - del self.password del self.repeat_password + return self class UserInDB(User): - """Schema with the full info about user from the database""" - hashed_password: str - role: str + """Schema to represent a user from the database.""" + + password: str = Field(description="Hashed password of the user") + role: str = Field(description="Role of the user (admin, user, etc.)") From c53ba36720499c0df70549b1a27dbdfb8e2e8d88 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:01:03 +0300 Subject: [PATCH 34/68] Update dependencies to validate tokens --- src/app/user/dependencies.py | 89 +++++++++++++++--------------------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/src/app/user/dependencies.py b/src/app/user/dependencies.py index 1213e96..a1f9850 100644 --- a/src/app/user/dependencies.py +++ b/src/app/user/dependencies.py @@ -1,31 +1,25 @@ from typing import Annotated -from fastapi import Cookie, Depends +from fastapi import Depends, Request from fastapi.security import OAuth2PasswordRequestForm -from redis.asyncio import Redis -from .auth import decode_token, verify_password, OAuth2PasswordBearerWithCookie +from app.main.dependencies import DbSession, RedisT +from app.main.settings import settings + +from .auth import get_token_payload, verify_password from .exceptions import ExpiredTokenError, IncorrectPasswordError +from .middlewares import OAuth2PasswordBearerWithCookie from .models import User from .schemas import TokenPayload, UserInDB -from .settings import ( - ACCESS_TOKEN_COOKIE_EXPIRATION_TIME, - REFRESH_TOKEN_COOKIE_EXPIRATION_TIME, -) -from app.main.db import DBType -from app.main.dependencies import get_db, get_redis - oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="user/login") async def authenticate_user( form: Annotated[OAuth2PasswordRequestForm, Depends()], - db: Annotated[DBType, Depends(get_db)] + db: DbSession, ) -> UserInDB: - """ - Return info about user from the database and verify one's - password. + """Return info about user from the database verifying one's password. :param form: input info about user from the form :param db: DB session @@ -33,67 +27,60 @@ async def authenticate_user( :return: info about user from the database """ user = await User.get(db, form.username) - if not verify_password(form.password, user.hashed_password): + if not verify_password(form.password, user.password): raise IncorrectPasswordError return user -async def decode_user_token(token: Annotated[str, Depends(oauth2_scheme)]) -> TokenPayload: - """ - Decode user's JWT token and return its payload. - - NOTE: depending on on what the OAuth2 scheme returns, it can be - either access or refresh token. +async def decode_token( + token: Annotated[str, Depends(oauth2_scheme)], +) -> TokenPayload: + """Decode JWT token and return its payload. :param token: user's JWT token :return: payload of the token """ - return decode_token(token) + return get_token_payload(token) -async def verify_token( - redis: Annotated[Redis, Depends(get_redis)], - access_token: Annotated[str | None, Cookie()] = None, - refresh_token: Annotated[str | None, Cookie()] = None, -) -> None: - """ - Verify if the access and refresh tokens (if present) - aren't in the blacklist of tokens. The tokens are retrieved - from the corresponding cookies. +async def verify_token_against_blacklist(request: Request, redis: RedisT) -> None: + """Check if JWT tokens aren't blacklisted. + :param request: FastAPI's Request object + :raises ExpiredTokenError: if any token is blacklisted :param redis: Redis client - :param refresh_token: refresh token cookie - :param access_token: access token cookie """ - if not access_token and not refresh_token: + if not request.cookies: return async with redis.pipeline() as pipe: - if access_token: + if access_token := request.cookies.get("access_token"): pipe.exists(access_token) - if refresh_token: + if refresh_token := request.cookies.get("refresh_token"): pipe.exists(refresh_token) if any(await pipe.execute()): raise ExpiredTokenError -async def invalidate_tokens( - redis: Annotated[Redis, Depends(get_redis)], - access_token: Annotated[str | None, Cookie()] = None, - refresh_token: Annotated[str | None, Cookie()] = None, -) -> None: - """ - Add access token and refresh token from cookies into - blacklist to avoid reusage of these tokens after log out. +async def add_tokens_to_blacklist(request: Request, redis: RedisT) -> None: + """Add access and/or refresh tokens into black list. + + Tokens are blacklisted to avoid their reuse. + + NOTE (1): tokens are blacklisted until their corresponding + cookies aren't expired. + :param request: FastAPI's Request object :param redis: Redis client - :param refresh_token: refresh token cookie - :param access_token: access token cookie. """ - if not access_token and not refresh_token: + if not request.cookies: return async with redis.pipeline() as pipe: - if access_token: - pipe.setex(access_token, ACCESS_TOKEN_COOKIE_EXPIRATION_TIME, "blacklist") - if refresh_token: - pipe.setex(refresh_token, REFRESH_TOKEN_COOKIE_EXPIRATION_TIME, "blacklist") + if access_token := request.cookies.get("access_token"): + pipe.setex( + access_token, settings.access_token_cookie_expiration_time, "blacklist" + ) + if refresh_token := request.cookies.get("refresh_token"): + pipe.setex( + refresh_token, settings.refresh_token_cookie_expiration_time, "blacklist" + ) await pipe.execute() From 84c5d3d92a4c452e915292e7836929117c182d30 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:02:12 +0300 Subject: [PATCH 35/68] Update cache settings for 'get_token_payload' function --- src/app/user/auth.py | 109 ++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 73 deletions(-) diff --git a/src/app/user/auth.py b/src/app/user/auth.py index adc4442..6329982 100644 --- a/src/app/user/auth.py +++ b/src/app/user/auth.py @@ -1,33 +1,26 @@ import json import re from functools import lru_cache +from typing import TYPE_CHECKING, cast -from fastapi.openapi.models import OAuthFlows, OAuthFlowPassword -from fastapi.security import OAuth2 -from fastapi.security.utils import get_authorization_scheme_param -from jose import jwt, JWTError +from jose import JWTError, jwt from passlib.context import CryptContext -from starlette.requests import Request -from .constants import TokenType as TT, UserRole as UR -from .exceptions import AuthenticationError, InvalidCredentialsError +from app.main.settings import settings + +from .exceptions import InvalidCredentialsError from .schemas import TokenPayload -from .settings import ( - JWT_SECRET_KEY, - JWT_ALGORITHM, - TOKEN_SCHEME, - USER_POLICY_FILE, -) +if TYPE_CHECKING: + from .constants import TokenType, UserRole -Policy = dict[str, dict[str, list[str]]] +type PolicyT = dict[str, dict[str, list[str]]] pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def generate_password_hash(plain_password: str) -> str: - """ - Generate hash of the plain password. + """Generate hash of the plain password. :param plain_password: plain password :return: hash of the password @@ -36,8 +29,7 @@ def generate_password_hash(plain_password: str) -> str: def verify_password(plain_password: str, hashed_password: str) -> bool: - """ - Verify plain and hashed password. + """Verify plain and hashed password. :param plain_password: plain password :param hashed_password: hashed password @@ -46,9 +38,8 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) -def generate_token(token_type: TT, username: str, role: str) -> str: - """ - Generate a JWT token of the specific type and claims. +def generate_token(token_type: "TokenType", username: str, role: str) -> str: + """Generate a JWT token of the specific type and claims. :param token_type: type of the token (access or refresh) :param username: username to generate the token for @@ -56,78 +47,50 @@ def generate_token(token_type: TT, username: str, role: str) -> str: :return: JWT token """ payload = TokenPayload(sub=username, typ=token_type, role=role) - token = jwt.encode(payload.model_dump(), JWT_SECRET_KEY, JWT_ALGORITHM) - return f"{TOKEN_SCHEME} {token}" + token = jwt.encode( + claims=payload.model_dump(), + key=settings.jwt_secret_key, + algorithm=settings.jwt_algorithm, + ) + return f"{settings.token_scheme} {token}" -@lru_cache(maxsize=100_000) -def decode_token(token: str) -> TokenPayload: - """ - Decode the JWT token and return its payload. +@lru_cache(maxsize=settings.token_payload_max_cache_hits) +def get_token_payload(token: str) -> TokenPayload: + """Decode the JWT token and return its payload. :param token: JWT token :raises InvalidCredentialsError: if the token can't be decoded :return: payload of the token """ try: - payload = jwt.decode(token, JWT_SECRET_KEY, [JWT_ALGORITHM]) - except JWTError: - raise InvalidCredentialsError + payload = jwt.decode( + token, + key=settings.jwt_secret_key, + algorithms=[settings.jwt_algorithm], + ) + except JWTError as e: + raise InvalidCredentialsError from e return TokenPayload.model_construct(**payload) @lru_cache -def verify_access(role: UR, url: str) -> bool: - """ - Verify if the user can access the URL according to the - role of the user. +def verify_access(role: "UserRole", url: str) -> bool: + """Verify if the user can access the URL according to one's role. :param role: role of the user :param url: target URL to check access to :return: boolean result of the verification """ - policy = get_user_policy() + policy = read_user_policy() return any(re.match(pattern, url) for pattern in policy[role]["locations"]) @lru_cache -def get_user_policy() -> Policy: - """ - Read user's policies from the JSON file - - :return: dict with user's policies from the file - """ - with open(USER_POLICY_FILE) as f: - return json.load(f) +def read_user_policy() -> PolicyT: + """Read user policy file and return its content as JSON. - -class OAuth2PasswordBearerWithCookie(OAuth2): - """ - OAuth2 flow for authentication which uses cookies (instead of headers) - to obtain tokens (access and refresh) from. - An instance of it would be used as a dependency. + :return: user policy as JSON """ - def __init__( - self, - tokenUrl: str, - scheme_name: str | None = None, - scopes: dict[str, str] | None = None, - auto_error: bool = True, - ) -> None: - flows = OAuthFlows( - password=OAuthFlowPassword(tokenUrl=tokenUrl, scopes=scopes or {}) - ) - super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error) - - async def __call__(self, request: Request) -> str | None: - # Check if the access token isn't expired/corrupted - access_token_cookie = request.cookies.get("access_token") - access_token_scheme, access_token = get_authorization_scheme_param(access_token_cookie) - if access_token or access_token_scheme == TOKEN_SCHEME: - return access_token - # Check if the refresh token isn't expired/corrupted - refresh_token_cookie = request.cookies.get("refresh_token") - refresh_token_scheme, refresh_token = get_authorization_scheme_param(refresh_token_cookie) - if refresh_token or refresh_token_scheme == TOKEN_SCHEME: - return refresh_token - raise AuthenticationError + with settings.user_policy_file.open() as f: + return cast("PolicyT", json.load(f)) From bee95e3887de5d94527c6a3ea5c6bd3fa8b654b4 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:02:55 +0300 Subject: [PATCH 36/68] Move base exception class to 'main.exceptions' --- src/app/user/exceptions.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/app/user/exceptions.py b/src/app/user/exceptions.py index 4335ee2..475b0f7 100644 --- a/src/app/user/exceptions.py +++ b/src/app/user/exceptions.py @@ -1,23 +1,6 @@ -from fastapi import HTTPException, status +from fastapi import status -from .settings import TOKEN_SCHEME - - -class BaseHTTPException(HTTPException): - detail: str - status_code: int = status.HTTP_401_UNAUTHORIZED - headers: dict[str, str] | None = {"WWW-Authenticate": TOKEN_SCHEME} - - def __init__(self, - status_code: int | None = None, - detail: str | None = None, - headers: dict[str, str] | None = None - ) -> None: - super().__init__( - status_code or self.status_code, - detail or self.detail, - headers or self.headers - ) +from app.main.exceptions import BaseHTTPException class InvalidCredentialsError(BaseHTTPException): @@ -39,19 +22,11 @@ class IncorrectPasswordError(BaseHTTPException): class PasswordsDontMatchError(BaseHTTPException): status_code: int = status.HTTP_422_UNPROCESSABLE_ENTITY detail: str = "Passwords don't match" - headers: None = None class InactiveUserError(BaseHTTPException): status_code: int = status.HTTP_400_BAD_REQUEST detail: str = "User is inactive" - headers: None = None - - -class UserAlreadyExistsError(BaseHTTPException): - status_code: int = status.HTTP_422_UNPROCESSABLE_ENTITY - detail: str = "User already exists" - headers: None = None class IncorrectTokenTypeError(BaseHTTPException): @@ -66,4 +41,3 @@ class ExpiredTokenError(BaseHTTPException): class PermissionDenied(BaseHTTPException): detail: str = "Permission denied to access this page" status_code: int = status.HTTP_403_FORBIDDEN - headers: None = None From 7c0c310dfb754b155587e857424025d4c94d9579 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:04:14 +0300 Subject: [PATCH 37/68] Update docstrings. Fix Ruff and Mypy errors --- src/app/user/routes.py | 95 +++++++++++++----------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/src/app/user/routes.py b/src/app/user/routes.py index 66ce42e..db99f2e 100644 --- a/src/app/user/routes.py +++ b/src/app/user/routes.py @@ -2,108 +2,76 @@ from fastapi import APIRouter, Depends, Header, Response, status +from app.main.dependencies import DbSession + from .auth import generate_token, verify_access -from .constants import CookieType as CT, TokenType as TT -from ..main.db import DBType +from .constants import CookieType, TokenType from .dependencies import ( + add_tokens_to_blacklist, authenticate_user, - decode_user_token, - invalidate_tokens, - verify_token, + decode_token, + verify_token_against_blacklist, ) from .exceptions import InactiveUserError, PermissionDenied from .models import User as DBUser from .schemas import NewUser, Token, TokenPayload, User, UserCookie, UserInDB -from app.main.dependencies import get_db - router = APIRouter(tags=["user"]) @router.post( "/logout", - description="Log out the authenticated user", status_code=status.HTTP_205_RESET_CONTENT, response_class=Response, - dependencies=[Depends(invalidate_tokens)], + dependencies=[Depends(add_tokens_to_blacklist)], ) async def logout(response: Response) -> None: - """ - Log out the authenticated user. - - When the user logs out, one's authorization cookies are - deleted and the tokens are temporarily added into into the - blacklist (Redis) to avoid their reusage. + """Log out the authenticated user. - :param response: response instance - :return: empty response without authentication cookies + Once the user is logged out, the authorization + cookies are deleted and the tokens are blacklisted. """ - for cookie in CT: + for cookie in CookieType: response.delete_cookie(cookie.name) -@router.post( - "/login", - description="Authenitcate the user", - response_model=Token, -) +@router.post("/login") async def login( response: Response, - user: Annotated[UserInDB, Depends(authenticate_user)] + user: Annotated[UserInDB, Depends(authenticate_user)], ) -> Token: - """ - Authenticate the user generating authorization cookies - (JWT access and refresh tokens). - - TODO: implement the logic to deal with inactive users. - - :param response: response instance - :param user: user's authorization credentials - :return: a pair of access and refresh tokens - """ + """Authenticate the user generating authorization cookies.""" if not user.is_active: raise InactiveUserError - access_token = generate_token(TT.access, user.username, user.role) - refresh_token = generate_token(TT.refresh, user.username, user.role) - access_token_cookie = UserCookie(key=CT.access_token, value=access_token) - refresh_token_cookie = UserCookie(key=CT.refresh_token, value=refresh_token) + access_token = generate_token(TokenType.access, user.username, user.role) + refresh_token = generate_token(TokenType.refresh, user.username, user.role) + access_token_cookie = UserCookie(key=CookieType.access_token, value=access_token) + refresh_token_cookie = UserCookie(key=CookieType.refresh_token, value=refresh_token) response.set_cookie(**access_token_cookie.model_dump()) response.set_cookie(**refresh_token_cookie.model_dump()) return Token.model_construct(access_token=access_token, refresh_token=refresh_token) -@router.post( - "/signup", - description="Signup a new user", - response_model=User, -) -async def signup(user: NewUser, db: Annotated[DBType, Depends(get_db)]) -> NewUser: - """ - Sign up a new user. - - :param user: input info about user - :param db: database client - :return: info about new user - """ - return await DBUser.create(db, user, hashed_password=user._hashed_password) +@router.post("/signup", response_model=User) +async def signup(user: NewUser, db: DbSession) -> NewUser: + """Sign up a user.""" + return await DBUser.create(db, user) @router.get( "/auth", - description="Verify authenticated user's credentials (tokens)", - dependencies=[Depends(verify_token)], + dependencies=[Depends(verify_token_against_blacklist)], status_code=status.HTTP_204_NO_CONTENT, include_in_schema=False, ) async def auth( response: Response, - payload: Annotated[TokenPayload, Depends(decode_user_token)], + payload: Annotated[TokenPayload, Depends(decode_token)], x_original_uri: Annotated[str | None, Header()] = None, ) -> None: - """ - Verify user's authorization tokens (access or refresh). + """Verify authorization tokens (access or refresh). - NOTE: + Note: 1) it's used by Nginx Subrequest module to allow/disallow further access to the protected resources; 2) if the refresh token is received, a new access token will @@ -111,15 +79,10 @@ async def auth( 3) there's a step to check whether the user has access to the admin resources. - :param response: response instance - :param payload: payload of the token - :param x_original_uri: value of the header X-Original-URI - :raises PermissionDenied: unless user has permissions to access the URL - :return: response """ if x_original_uri and not verify_access(payload.role, x_original_uri): raise PermissionDenied - if payload.typ == TT.refresh: - access_token = generate_token(TT.access, payload.sub, payload.role) - access_token_cookie = UserCookie(key=CT.access_token, value=access_token) + if payload.typ == TokenType.refresh: + access_token = generate_token(TokenType.access, payload.sub, payload.role) + access_token_cookie = UserCookie(key=CookieType.access_token, value=access_token) response.set_cookie(**access_token_cookie.model_dump()) From a9c3a08e753baa1dd7fe7335e01f9da1d5141ebe Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:04:32 +0300 Subject: [PATCH 38/68] Remove unused files --- poetry.lock | 1345 -------------------------------------------- src/scripts/run.sh | 10 - 2 files changed, 1355 deletions(-) delete mode 100644 poetry.lock delete mode 100755 src/scripts/run.sh diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 1c7b49c..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1345 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[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.13.2" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, - {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" - -[package.extras] -tz = ["backports.zoneinfo"] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -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"}, -] - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, -] - -[package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] - -[[package]] -name = "bcrypt" -version = "4.0.1" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.6" -files = [ - {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, - {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, - {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, - {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.5.4" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, -] - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "ecdsa" -version = "0.19.0" -description = "ECDSA cryptographic signature library (pure python)" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" -files = [ - {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, - {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, -] - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "email-validator" -version = "2.1.2" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.1.2-py3-none-any.whl", hash = "sha256:d89f6324e13b1e39889eab7f9ca2f91dc9aebb6fa50a6d8bd4329ab50f251115"}, - {file = "email_validator-2.1.2.tar.gz", hash = "sha256:14c0f3d343c4beda37400421b39fa411bbe33a75df20825df73ad53e06a9f04c"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - -[[package]] -name = "faker" -version = "26.0.0" -description = "Faker is a Python package that generates fake data for you." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Faker-26.0.0-py3-none-any.whl", hash = "sha256:886ee28219be96949cd21ecc96c4c742ee1680e77f687b095202c8def1a08f06"}, - {file = "Faker-26.0.0.tar.gz", hash = "sha256:0f60978314973de02c00474c2ae899785a42b2cf4f41b7987e93c132a2b8a4a9"}, -] - -[package.dependencies] -python-dateutil = ">=2.4" - -[[package]] -name = "fakeredis" -version = "2.23.3" -description = "Python implementation of redis API, can be used for testing purposes." -optional = false -python-versions = "<4.0,>=3.7" -files = [ - {file = "fakeredis-2.23.3-py3-none-any.whl", hash = "sha256:4779be828f4ebf53e1a286fd11e2ffe0f510d3e5507f143d644e67a07387d759"}, - {file = "fakeredis-2.23.3.tar.gz", hash = "sha256:0c67caa31530114f451f012eca920338c5eb83fa7f1f461dd41b8d2488a99cba"}, -] - -[package.dependencies] -redis = ">=4" -sortedcontainers = ">=2,<3" - -[package.extras] -bf = ["pyprobables (>=0.6,<0.7)"] -cf = ["pyprobables (>=0.6,<0.7)"] -json = ["jsonpath-ng (>=1.6,<2.0)"] -lua = ["lupa (>=2.1,<3.0)"] -probabilistic = ["pyprobables (>=0.6,<0.7)"] - -[[package]] -name = "fastapi-slim" -version = "0.111.1" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi_slim-0.111.1-py3-none-any.whl", hash = "sha256:ac29948dcbf84cc78d68ed2c4df4e695ac265cf53c339e5794008476e9befbbb"}, - {file = "fastapi_slim-0.111.1.tar.gz", hash = "sha256:f799a60658f56c49fe3842eb534730fabe1168731c0b407b98a042c8d57be39d"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.38.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "gunicorn" -version = "22.0.0" -description = "WSGI HTTP Server for UNIX" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, - {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "itsdangerous" -version = "2.2.0" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.8" -files = [ - {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, - {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "mako" -version = "1.3.5" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mypy" -version = "1.10.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, - {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, - {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, - {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, - {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, - {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, - {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, - {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, - {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, - {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, - {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, - {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, - {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, - {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, - {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, - {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, - {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, - {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, - {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "passlib" -version = "1.7.4" -description = "comprehensive password hashing framework supporting over 30 schemes" -optional = false -python-versions = "*" -files = [ - {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, - {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, -] - -[package.extras] -argon2 = ["argon2-cffi (>=18.2.0)"] -bcrypt = ["bcrypt (>=3.1.0)"] -build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] -totp = ["cryptography"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, -] - -[[package]] -name = "pydantic" -version = "2.8.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.20.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.2.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2.0" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "5.0.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-faker" -version = "2.0.0" -description = "Faker integration with the pytest framework." -optional = false -python-versions = "*" -files = [ - {file = "pytest-faker-2.0.0.tar.gz", hash = "sha256:6b37bb89d94f96552bfa51f8e8b89d32addded8ddb58a331488299ef0137d9b6"}, -] - -[package.dependencies] -Faker = ">=0.7.3" - -[[package]] -name = "python-dateutil" -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" -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"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-jose" -version = "3.3.0" -description = "JOSE implementation in Python" -optional = false -python-versions = "*" -files = [ - {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, - {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, -] - -[package.dependencies] -ecdsa = "!=0.15" -pyasn1 = "*" -rsa = "*" - -[package.extras] -cryptography = ["cryptography (>=3.4.0)"] -pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] -pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] - -[[package]] -name = "python-multipart" -version = "0.0.9" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, -] - -[package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - -[[package]] -name = "redis" -version = "5.0.7" -description = "Python client for Redis database and key-value store" -optional = false -python-versions = ">=3.7" -files = [ - {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, - {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, -] - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] - -[[package]] -name = "rich" -version = "13.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruff" -version = "0.4.10" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = false -python-versions = "*" -files = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] - -[[package]] -name = "sqladmin" -version = "0.18.0" -description = "SQLAlchemy admin for FastAPI and Starlette" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sqladmin-0.18.0-py3-none-any.whl", hash = "sha256:e1c306d7e3ab7752b9b47fc164154db2bed5e99d348d03427e459f70a53f7f69"}, - {file = "sqladmin-0.18.0.tar.gz", hash = "sha256:9a758eef19a303e49ab674f3329d6e3c0c426d8460e6cb023d3c476af6692a12"}, -] - -[package.dependencies] -jinja2 = "*" -python-multipart = "*" -sqlalchemy = ">=1.4" -starlette = "*" -wtforms = ">=3.1,<3.2" - -[package.extras] -full = ["itsdangerous"] - -[[package]] -name = "sqlalchemy" -version = "2.0.31" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "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\")"} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "starlette" -version = "0.37.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "typer" -version = "0.12.3" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -files = [ - {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, - {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -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"}, -] - -[[package]] -name = "uvicorn" -version = "0.30.3" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -files = [ - {file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"}, - {file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "wtforms" -version = "3.1.2" -description = "Form validation and rendering for Python web development." -optional = false -python-versions = ">=3.8" -files = [ - {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"}, - {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, -] - -[package.dependencies] -markupsafe = "*" - -[package.extras] -email = ["email-validator"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "b3c8e14c75a78915f584641a38a89f2f65a39d82de4f4e99d2c4602a6a7a384f" diff --git a/src/scripts/run.sh b/src/scripts/run.sh deleted file mode 100755 index ba1723b..0000000 --- a/src/scripts/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -set -e - -# Run app -gunicorn app.main.app:app \ - --workers ${GUNICORN_WORKERS} \ - --threads ${GUNICORN_THREADS} \ - --worker-class uvicorn.workers.UvicornWorker \ - --bind ${APP_HOST}:${APP_PORT} From 3447267d023e60b709decb4eda2ffa92ecbee673 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:42:59 +0300 Subject: [PATCH 39/68] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac92790..3210d62 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ venv __pycache__ .pytest_cache .env +.coverage notes.txt test.db \ No newline at end of file From 429b883a4438d26298c65ff9e19bd0764f9bf9ba Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:43:11 +0300 Subject: [PATCH 40/68] Update config for Ruff and Pytest --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9a6d03c..9c4d0db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,11 +58,13 @@ lint.ignore = [ [tool.ruff.lint.per-file-ignores] "src/tests/*" = ["D103", "S101"] "src/app/user/exceptions.py" = ["D101"] +"src/alembic/versions/*" = ["D103", "D400", "D415", "INP001"] [tool.mypy] python_version = "3.12" strict = true exclude = [".venv"] +files = "src/" [tool.pytest.ini_options] addopts = """ @@ -70,6 +72,7 @@ addopts = """ --durations-min=0.1 --verbose --maxfail=1 + --cov """ filterwarnings = [ "ignore::DeprecationWarning", From d9822be2708f3aea2843ff6c1734cff2de85e726 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:43:25 +0300 Subject: [PATCH 41/68] Fix Mypy error --- src/alembic/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alembic/env.py b/src/alembic/env.py index 9cc3876..eb5a3ea 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -54,7 +54,7 @@ async def run_migrations_online() -> None: and associate a connection with the context. """ connectable = async_engine_from_config( - config.get_section(config.config_ini_section), # type: ignore + config.get_section(config.config_ini_section) or {}, prefix="sqlalchemy.", poolclass=NullPool, ) From 3a2a80f56e5130e8d656724dc165a19c80d59a39 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:43:52 +0300 Subject: [PATCH 42/68] Update steps to install python and dependencies --- .github/workflows/checks.yml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3045955..f701291 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,39 +1,40 @@ -name: code checks +name: Code Checks on: push: branches: [ main ] - paths: ['src/**'] pull_request: branches: [ main ] jobs: checks: name: Test Code - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: 3.12 + python-version: "3.12" - - name: Install python packages - run: | - pip install --upgrade pip - pip install setuptools poetry - poetry install --no-root + - name: Install uv + uses: astral-sh/setup-uv@v1 + with: + version: "0.7.13" + + - name: Install dependencies + run: uv sync --locked --all-extras --group test - - name: Running Ruff - run: poetry run ruff check + - name: Run Ruff + run: uv run ruff check - - name: Running Mypy - run: poetry run mypy . + - name: Run Mypy + run: uv run mypy - - name: Running Pytest with Coverage - run: poetry run pytest --cov + - name: Run Pytest with Coverage + run: uv run pytest --cov - name: Creating coverage folder run: mkdir -p coverage From 04f7e0bbaccbdc3c703def0b2a29d7de6e554239 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 1 Sep 2025 21:44:04 +0300 Subject: [PATCH 43/68] Fix Mypy and Ruff errors --- src/tests/app/user/test_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/app/user/test_routes.py b/src/tests/app/user/test_routes.py index e55ab92..f11d0f2 100644 --- a/src/tests/app/user/test_routes.py +++ b/src/tests/app/user/test_routes.py @@ -114,7 +114,7 @@ async def deactivate_user() -> None: signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - main_app = cast("FastAPI", client._transport.app) # type: ignore + main_app = cast("FastAPI", client._transport.app) # type: ignore # noqa: PGH003 main_app.dependency_overrides[authenticate_user] = deactivate_user login_resp = await client.post( "/login", From 2a29d54f2dacb841c2c651f4f6cf5d7d493788ce Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 2 Sep 2025 11:14:36 +0300 Subject: [PATCH 44/68] Update params for gunicorn --- run.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/run.sh b/run.sh index 0f7dd7d..b5b213c 100644 --- a/run.sh +++ b/run.sh @@ -7,12 +7,16 @@ echo "Running database migrations ..." alembic -c alembic/alembic.ini upgrade head if [ "$DEBUG" = "1" ]; then - echo "Running application in debug mode ..." + echo "Running application in the debug mode ..." uvicorn app.main.app:app \ - --host ${APP_HOST:-app} \ - --port ${APP_PORT:-8000} \ + --host ${APP_HOST} \ + --port ${APP_PORT} \ --reload else - echo "Running application in production mode ..." - gunicorn app.main.app:app -c gunicorn.conf.py + echo "Running application in the production mode ..." + gunicorn app.main.app:app \ + --bind ${APP_HOST}:${APP_PORT} \ + --workers ${GUNICORN_WORKERS} \ + --worker-class uvicorn.workers.UvicornWorker \ + --log-level warning fi From c3c8a4c08b2b327939b3aac8b57aed265d614f1f Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 2 Sep 2025 11:15:08 +0300 Subject: [PATCH 45/68] Add 'docs_url' param to the FastAPI settings --- src/app/main/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/main/settings.py b/src/app/main/settings.py index deebd1f..127c1da 100644 --- a/src/app/main/settings.py +++ b/src/app/main/settings.py @@ -13,6 +13,7 @@ class FastAPIKwargs(TypedDict): description: str version: str debug: bool + docs_url: str class LoggingKwargs(TypedDict): @@ -49,6 +50,7 @@ class Settings(BaseSettings): description: str = "RBAC JWT Cookies-based API Gateway" version: str = "0.0.1" debug: bool = True + docs_url: str = "/" # Admin app setting admin_base_url: str = "/admin" @@ -111,6 +113,7 @@ def fastapi_kwargs(self) -> FastAPIKwargs: description=self.description, version=self.version, debug=self.debug, + docs_url=self.docs_url, ) @property From dcd2343b123c3941056e90e4bca255b26c1ad864 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 2 Sep 2025 11:15:27 +0300 Subject: [PATCH 46/68] Remove step to copy config for gunicorn --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cfa21ef..e0accad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,5 +9,5 @@ COPY ./pyproject.toml ./uv.lock ./run.sh ./ RUN apk add --no-cache curl && \ uv sync --locked && \ chmod +x ./run.sh -COPY ./src ./gunicorn.conf.py ./ +COPY ./src ./ CMD ["./run.sh"] From 062cd94d968e51919d36f4fc56efb3a0c515ac1a Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 2 Sep 2025 11:15:56 +0300 Subject: [PATCH 47/68] Replace '/docs' location with '/' --- proxy/default.conf.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/default.conf.tpl b/proxy/default.conf.tpl index f296307..df11c81 100644 --- a/proxy/default.conf.tpl +++ b/proxy/default.conf.tpl @@ -22,8 +22,8 @@ server { proxy_pass http://auth_api/$openapi; } - location = /docs { - proxy_pass http://auth_api/docs; + location = / { + proxy_pass http://auth_api/; } location = /health { From af4babe04aa16b2fd8b4abc855197f1d20aa790f Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 2 Sep 2025 11:16:09 +0300 Subject: [PATCH 48/68] Remove config for gunicorn --- gunicorn.conf.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 gunicorn.conf.py diff --git a/gunicorn.conf.py b/gunicorn.conf.py deleted file mode 100644 index 9f32890..0000000 --- a/gunicorn.conf.py +++ /dev/null @@ -1,12 +0,0 @@ -import os - -host = os.getenv("APP_HOST", "app") -port = os.getenv("APP_PORT", "8000") -bind = f"{host}:{port}" -worker_class = "uvicorn.workers.UvicornWorker" -forwarded_allow_ips = "*" - -# Production settings -if os.getenv("DEBUG") == "0": - loglevel = "warning" - workers = int(os.getenv("GUNICORN_WORKERS", "2")) From ddafd4760567bc0a3c9379d89aac30939f81151d Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Sat, 6 Sep 2025 10:07:33 +0300 Subject: [PATCH 49/68] Inline definition of 'authorized_client' fixture --- src/tests/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index c143d34..4c15470 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -176,10 +176,7 @@ async def authorize_client(client: AsyncClient, user: NewUser) -> AsyncClient: @pytest.fixture -async def authorized_client( - client: AsyncClient, - db_user: NewUser, -) -> AsyncClient: +async def authorized_client(client: AsyncClient, db_user: NewUser) -> AsyncClient: """Async HTTP-client with authorization cookies for a regular user. NOTE: it's used for integration tests. From 417832266d986f5e40ef7e36a763b0f485957a00 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Sat, 6 Sep 2025 10:35:33 +0300 Subject: [PATCH 50/68] Add 'type_annotation_map' for 'Base' class --- src/app/main/db.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/main/db.py b/src/app/main/db.py index 05053f9..a9f4659 100644 --- a/src/app/main/db.py +++ b/src/app/main/db.py @@ -1,6 +1,7 @@ -from typing import ClassVar +from datetime import datetime +from typing import Any, ClassVar -from sqlalchemy import MetaData +from sqlalchemy import DateTime, MetaData, String from sqlalchemy.ext.asyncio import ( AsyncSession, async_sessionmaker, @@ -23,3 +24,7 @@ class Base(DeclarativeBase): """Base declarative class.""" metadata: ClassVar[MetaData] = metadata + type_annotation_map: ClassVar[dict[type, Any]] = { + datetime: DateTime(timezone=True), + str: String(255), + } From c3b1085f331cfa4e0a271ecb7af1c13db82a445b Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Sat, 6 Sep 2025 10:35:54 +0300 Subject: [PATCH 51/68] Remove timezone from datetime-based attributes --- src/app/user/models.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/user/models.py b/src/app/user/models.py index bb68d68..bae32e8 100644 --- a/src/app/user/models.py +++ b/src/app/user/models.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import Boolean, DateTime, func, select +from sqlalchemy import Boolean, func, select from sqlalchemy.orm import Mapped, mapped_column from app.main.db import Base @@ -30,12 +30,8 @@ class User(Base): password: Mapped[str] role: Mapped[str] = mapped_column(default=UserRole.user.name, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True) - created_at: Mapped[datetime] = mapped_column( - DateTime(timezone=True), - server_default=func.now(), - ) + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( - DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), ) From 987a8ef16b5f959a9aedb661c186a163e76d9e5b Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Sun, 7 Sep 2025 11:43:02 +0300 Subject: [PATCH 52/68] Inline definition of 'decode_token' function --- src/app/user/dependencies.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/user/dependencies.py b/src/app/user/dependencies.py index a1f9850..30235a6 100644 --- a/src/app/user/dependencies.py +++ b/src/app/user/dependencies.py @@ -32,9 +32,7 @@ async def authenticate_user( return user -async def decode_token( - token: Annotated[str, Depends(oauth2_scheme)], -) -> TokenPayload: +async def decode_token(token: Annotated[str, Depends(oauth2_scheme)]) -> TokenPayload: """Decode JWT token and return its payload. :param token: user's JWT token From 159cd384a2c2302504be16e16d86852cb1851698 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 9 Sep 2025 22:36:01 +0300 Subject: [PATCH 53/68] Add error handler for uncaught errors --- src/app/user/errors.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/user/errors.py b/src/app/user/errors.py index 722b81a..56291d5 100644 --- a/src/app/user/errors.py +++ b/src/app/user/errors.py @@ -30,3 +30,14 @@ async def validation_error_handler( error_message = f"{error['msg']}. Field: {field}" client_message = {"detail": error_message} return JSONResponse(client_message, status.HTTP_422_UNPROCESSABLE_ENTITY) + + +async def unexpected_error_handler( + request: Request, + e: Exception, +) -> JSONResponse: + """Error handler for all uncaught exceptions.""" + logger = cast("Logger", request.app.state.logger) + logger.critical("Internal Server Error: %s", e) + client_message = {"error": "Service is temporarily unavailable"} + return JSONResponse(client_message, status.HTTP_500_INTERNAL_SERVER_ERROR) From d90d78f6269d54d7b82be63fbb5c694f3d8132c0 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 9 Sep 2025 22:36:16 +0300 Subject: [PATCH 54/68] Add error handler for uncaught errors to the app --- src/app/main/app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/main/app.py b/src/app/main/app.py index 554ebce..f87dd7a 100644 --- a/src/app/main/app.py +++ b/src/app/main/app.py @@ -8,7 +8,11 @@ from sqlalchemy.exc import IntegrityError from app.admin.app import init_admin_app -from app.user.errors import db_integrity_error_handler, validation_error_handler +from app.user.errors import ( + db_integrity_error_handler, + unexpected_error_handler, + validation_error_handler, +) from app.user.routes import router as user_router from .db import engine @@ -49,6 +53,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: exception_handlers={ IntegrityError: db_integrity_error_handler, RequestValidationError: validation_error_handler, + Exception: unexpected_error_handler, }, ) app.add_middleware(CORSMiddleware, **settings.cors_kwargs) From 977354b43592a515f305f4555156c62a7dcd30c1 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 9 Sep 2025 22:36:52 +0300 Subject: [PATCH 55/68] Fix missing 'app' argument in the lifespan function --- src/app/main/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/main/app.py b/src/app/main/app.py index f87dd7a..850ede8 100644 --- a/src/app/main/app.py +++ b/src/app/main/app.py @@ -22,7 +22,7 @@ @asynccontextmanager -async def lifespan(_: FastAPI) -> AsyncIterator[None]: +async def lifespan(app: FastAPI) -> AsyncIterator[None]: """Init and release objects on app startup and shutdown. On startup: From e0467f7a5e3c6dcc3065a51b0bc1589d809c150d Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 9 Sep 2025 22:42:38 +0300 Subject: [PATCH 56/68] Fix codestyle errors --- src/tests/conftest.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index 4c15470..0506caf 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -118,7 +118,10 @@ def user(faker: "Faker") -> NewUser: ) -async def create_user_by_role(user: NewUser, role: UserRole) -> NewUser: +async def create_user_by_role( + user: NewUser, + role: UserRole, +) -> NewUser: """Create a user with the specified role for further testing. :param user: info about user @@ -161,7 +164,10 @@ async def db_admin(user: NewUser) -> NewUser: return await create_user_by_role(user, role=UserRole.admin) -async def authorize_client(client: AsyncClient, user: NewUser) -> AsyncClient: +async def authorize_client( + client: AsyncClient, + user: NewUser, +) -> AsyncClient: """Authorize a user, setting authorization cookies into the client. :param client: async HTTP-client @@ -176,7 +182,10 @@ async def authorize_client(client: AsyncClient, user: NewUser) -> AsyncClient: @pytest.fixture -async def authorized_client(client: AsyncClient, db_user: NewUser) -> AsyncClient: +async def authorized_client( + client: AsyncClient, + db_user: NewUser, +) -> AsyncClient: """Async HTTP-client with authorization cookies for a regular user. NOTE: it's used for integration tests. From aa7816bd199aae5168d76dbe9bda1ad64152e120 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 12:59:17 +0300 Subject: [PATCH 57/68] Add asgi-lifespan package --- pyproject.toml | 3 ++- uv.lock | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c4d0db..274456c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "alembic>=1.16.4", + "asgi-lifespan>=2.1.0", "asyncpg>=0.30.0", "bcrypt==4.0.1", "email-validator>=2.3.0", @@ -76,4 +77,4 @@ addopts = """ """ filterwarnings = [ "ignore::DeprecationWarning", -] \ No newline at end of file +] diff --git a/uv.lock b/uv.lock index 349caa5..73f3b8b 100644 --- a/uv.lock +++ b/uv.lock @@ -51,6 +51,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] +[[package]] +name = "asgi-lifespan" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/da/e7908b54e0f8043725a990bf625f2041ecf6bfe8eb7b19407f1c00b630f7/asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308", size = 15627, upload-time = "2023-03-28T17:35:49.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" }, +] + [[package]] name = "asyncpg" version = "0.30.0" @@ -81,6 +93,7 @@ version = "0.0.1" source = { virtual = "." } dependencies = [ { name = "alembic" }, + { name = "asgi-lifespan" }, { name = "asyncpg" }, { name = "bcrypt" }, { name = "email-validator" }, @@ -117,6 +130,7 @@ test = [ [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.16.4" }, + { name = "asgi-lifespan", specifier = ">=2.1.0" }, { name = "asyncpg", specifier = ">=0.30.0" }, { name = "bcrypt", specifier = "==4.0.1" }, { name = "email-validator", specifier = ">=2.3.0" }, From e770ae9fccc7c08367952c91c6bb647fad5d6304 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 12:59:46 +0300 Subject: [PATCH 58/68] Update 'client' fixture to use 'LifespanManager' --- src/tests/conftest.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index 0506caf..49398e8 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING import pytest +from asgi_lifespan import LifespanManager from fakeredis import FakeAsyncRedis from fastapi import status from httpx import ASGITransport, AsyncClient, Cookies @@ -15,9 +16,11 @@ from app.main.app import app from app.main.db import Base -from app.main.dependencies import get_db, get_redis +from app.main.dependencies import get_db from app.main.settings import settings from app.user.constants import UserRole +from app.user.dependencies import authenticate_user +from app.user.exceptions import InactiveUserError from app.user.models import User from app.user.schemas import NewUser @@ -73,19 +76,17 @@ async def client(redis_client: "Redis") -> AsyncIterator[AsyncClient]: :param redis_client: fixture providing a client for Redis. :yield: async HTTP-client. """ - - async def override_get_redis() -> "Redis": - """Override dependency for API routes to interact with Redis.""" - return redis_client - - # use a different logger specifically for testing + # override app's state objects specifically for testing app.state.logger = logging.getLogger(settings.test_log_name) + app.state.engine = engine + app.state.redis = redis_client + app.dependency_overrides[get_db] = override_get_db - app.dependency_overrides[get_redis] = override_get_redis - transport = ASGITransport(app) - base_url = f"http://{settings.host_server_domain}" - async with AsyncClient(base_url=base_url, transport=transport) as ac: - yield ac + async with LifespanManager(app) as manager: + transport = ASGITransport(manager.app) + base_url = f"http://{settings.host_server_domain}" + async with AsyncClient(base_url=base_url, transport=transport) as ac: + yield ac @pytest.fixture(scope="session") @@ -118,6 +119,18 @@ def user(faker: "Faker") -> NewUser: ) +@pytest.fixture +async def deactivate_user() -> AsyncIterator[None]: + """Fixture to deactivate a user before one is authenticated.""" + + async def _deactivate_user() -> None: + raise InactiveUserError + + app.dependency_overrides[authenticate_user] = _deactivate_user + yield + del app.dependency_overrides[authenticate_user] + + async def create_user_by_role( user: NewUser, role: UserRole, From 81fb7e6724f8929f5c7fb1fab2a3621c2c534868 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 13:00:20 +0300 Subject: [PATCH 59/68] Update 'lifespan' function to check objects in the state --- src/app/main/app.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/main/app.py b/src/app/main/app.py index 850ede8..27c4691 100644 --- a/src/app/main/app.py +++ b/src/app/main/app.py @@ -4,7 +4,6 @@ from fastapi import FastAPI from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware -from redis.asyncio import ConnectionPool, Redis from sqlalchemy.exc import IntegrityError from app.admin.app import init_admin_app @@ -17,6 +16,7 @@ from .db import engine from .logger import configure_logging +from .redis import redis from .routes import router as default_router from .settings import settings @@ -27,24 +27,29 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: On startup: - init Redis client with connection pool; + - init Admin app with the specified database engine; On shutdown: - dispose Redis client with connection pool; - dispose database engine. + + NOTE: during testing state's objects are redeclared to adapt the + application to the testing environment. """ - redis_pool = ConnectionPool.from_url( - settings.redis_url, - decode_responses=True, - ) - redis_client = Redis(connection_pool=redis_pool) + redis_client = getattr(app.state, "redis", redis) + database_engine = getattr(app.state, "engine", engine) + + init_admin_app(app, database_engine) # set application state app.state.logger = configure_logging() app.state.redis = redis_client + yield + await redis_client.close() await redis_client.connection_pool.disconnect() - await engine.dispose() + await database_engine.dispose() app = FastAPI( @@ -59,5 +64,3 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app.add_middleware(CORSMiddleware, **settings.cors_kwargs) app.include_router(default_router) app.include_router(user_router) - -init_admin_app(app, engine) From fdf5ddf01d12b4d94ccdd1a82626602c6f8fecd2 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 13:00:48 +0300 Subject: [PATCH 60/68] Add test to check existence of the users page in the admin --- src/tests/app/admin/test_views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tests/app/admin/test_views.py b/src/tests/app/admin/test_views.py index d031540..eadaab7 100644 --- a/src/tests/app/admin/test_views.py +++ b/src/tests/app/admin/test_views.py @@ -17,6 +17,12 @@ async def test_admin_home(client: "AsyncClient") -> None: assert resp.status_code == status.HTTP_200_OK +async def test_admin_users_index(client: "AsyncClient") -> None: + resp = await client.get("/admin/user/list") + assert resp.status_code == status.HTTP_200_OK + assert '

Users

' in resp.text + + @pytest.mark.skipif(E2E_MODE_DISABLED, reason="E2E mode disabled") async def test_admin_home_as_unauthorized_user(e2e_client: "AsyncClient") -> None: resp = await e2e_client.get("/admin/") From b45958309703daa3c9a4e8b94f70cebe8e0895a7 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 13:01:22 +0300 Subject: [PATCH 61/68] Update 'test_inactive_user_login' test case --- src/tests/app/user/test_routes.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/tests/app/user/test_routes.py b/src/tests/app/user/test_routes.py index f11d0f2..53931d6 100644 --- a/src/tests/app/user/test_routes.py +++ b/src/tests/app/user/test_routes.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING import pytest from fastapi import status @@ -7,10 +7,8 @@ from app.main.settings import settings from app.user import exceptions as ex from app.user.constants import CookieType -from app.user.dependencies import authenticate_user if TYPE_CHECKING: - from fastapi import FastAPI from httpx import AsyncClient from app.user.schemas import NewUser @@ -107,22 +105,19 @@ async def test_user_login_success(client: "AsyncClient", user: "NewUser") -> Non assert "refreshToken" in login_resp_data -async def test_inactive_user_login(client: "AsyncClient", user: "NewUser") -> None: - async def deactivate_user() -> None: - raise ex.InactiveUserError - +async def test_inactive_user_login( + client: "AsyncClient", + user: "NewUser", + deactivate_user: None, # noqa: ARG001 +) -> None: signup_resp = await client.post("/signup", json=user.model_dump()) assert signup_resp.status_code == status.HTTP_200_OK - - main_app = cast("FastAPI", client._transport.app) # type: ignore # noqa: PGH003 - main_app.dependency_overrides[authenticate_user] = deactivate_user login_resp = await client.post( "/login", data={"username": user.username, "password": user.password}, ) assert login_resp.status_code == status.HTTP_400_BAD_REQUEST assert login_resp.json() == {"detail": ex.InactiveUserError.detail} - del main_app.dependency_overrides[authenticate_user] async def test_unauthorized_auth(client: "AsyncClient") -> None: From 2456259f972d584f19c38f44324851ec4a70d79a Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 10 Sep 2025 13:01:41 +0300 Subject: [PATCH 62/68] Add separate file to configure redis --- src/app/main/redis.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/app/main/redis.py diff --git a/src/app/main/redis.py b/src/app/main/redis.py new file mode 100644 index 0000000..3283d12 --- /dev/null +++ b/src/app/main/redis.py @@ -0,0 +1,9 @@ +from redis.asyncio import ConnectionPool, Redis + +from .settings import settings + +redis_connection_pool = ConnectionPool.from_url( + settings.redis_url, + decode_responses=True, +) +redis = Redis(connection_pool=redis_connection_pool) From 7f9f69ce9249c2d9848e66100632968da1300fd3 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 23 Sep 2025 20:56:36 +0300 Subject: [PATCH 63/68] Update README.md --- README.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8017877..be9788f 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,106 @@ [![Linter Ruff](https://img.shields.io/badge/Linter-Ruff-brightgreen)](https://github.com/charliermarsh/ruff) +## About +Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/), this project represents a simple cookies-based role-based authentication service using JWT tokens, built with FastAPI and managed by Nginx's [Auth Sub Request](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) module to verify users access to the resources of the API. -## Usage -Build production image: -```bash -docker-compose build . +## Features +- Access and refresh JWT tokens stored in secure cookies +- Role-based access control +- Automatic refreshment and invalidation of tokens +- Authentication based on Nginx's subrequest module +- Admin UI to manage users and roles +- CLI for creating users + + +## How to protect an endpoint +Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with a specific role. For example, let's say you have an endpoint `/test` that you want to protect it from the public access: + +### Step 1. Add a location block for the target to [default.conf.tpl](proxy/default.conf.tpl) Nginx configuration file: + + +```nginx + location = /test { + include /etc/nginx/snippets/auth_subrequest.conf; + proxy_pass http://auth_api/test; + } +``` + +The most important part in the location block the inclusion of the `auth_subrequest.conf` snippet, which contains the configuration for Nginx's subrequest authentication module. + + +### Step 2. Grant access to the endpoint only to users with a specific role. + +For this, you simple need to insert the endpoint into the `locations` attribute of the specified role in [policy.json](src/policy.json) file. For example, let's say that access to the `/test` endpoint should be granted only to users with the `moderator` role: + +```json + "moderator": { + "locations": [ + "/docs", + "/login", + "/logout", + "/test" + ] + } ``` -Build development image: +As a result, after authentication and authorization process, Nginx will automatically send an internal subrequest to the API for each request to the `/test` endpoint, verifying user's JWT token and role. If the user doesn't have access to thea protected endpoint, the API will respond with a `403 Forbidden` status code. + + +## System Requirements + +* Python 3.12 +* UV package manager +* Docker and Docker Compose plugin + + +## Configuration +There are a few configuration files that can be modified to customize the behavior of the application: + +* [proxy/default.conf.tpl](proxy/default.conf.tpl). This is the configuration file for Nginx. In this file you declare the endpoints (locations) that you want to protect in your API using Nginx's subrequest module. +* `src/policy.json`. Access control policy file. This is a simple JSON file that defines the roles and their associated permissions (i.e., which endpoints they can access). +* `.env`. File containing environment variables for configuring the application. It provides settings for: + * Main FastAPI application + * PostgreSQL for storing users data + * Redis for storing information about authentication tokens + * JWT tokens + * Nginx server + * Other settings + +**Note**: if the `.env` file is missing, create it by copying the `.env.example` file and modifying the values as needed. + + +## Deployment + +Once the configuration file is set up, you can deploy the application using Docker Compose: + ```bash - docker-compose build --build-arg DEV=true auth +docker compose up -d ``` -Run app: +The will start the following services: +* app - FastAPI application; +* db - PostgreSQL database to store users data; +* redis - Redis database to store authentication tokens data; +* nginx - Nginx for proxying API requests and handling authentication using the *Auth Subrequest* module. + + +## Usage + +Once all services are up and running, you can register the first user using the CLI. + +**Note**: use the CLI script below to register a user with a specified role, such as *admin* or *moderator*. + ```bash -docker-compose up -d +docker compose exec app python scripts/create_user.py ``` +After creating the user, navigate to `http://localhost/` to access the Swagger UI documentation of the API. From there, you can use the `/login` endpoint to authenticate and obtain a pair of JWT tokens that will be stored in the browser's cookies. You can then access protected endpoints based on the user's role. + -## Useful Links: +## References: - [OAuth2 with Password (and hashing), Bearer with JWT tokens¶](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/) - [FastAPI JWT Auth](https://indominusbyte.github.io/fastapi-jwt-auth/) - [Nginx. Authentication Based on Subrequest Result](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) - - [SQLAlchemy 2.0 Asynchronous I/O (asyncio)](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html) - - [SQLAlchemy 2.0 Table Configuration with Declarative](https://docs.sqlalchemy.org/en/20/orm/declarative_tables.html) - - [Github Actions. Pytest Coverage Comment](https://github.com/marketplace/actions/pytest-coverage-comment) - - [Github. FakeRedis](https://fakeredis.readthedocs.io/en/latest) - - [Redis Asyncio Examples](https://redis-py.readthedocs.io/en/stable/examples/asyncio_examples.html) - - [The mypy configuration file](https://mypy.readthedocs.io/en/stable/config_file.html) + From 520a487b0163b6b271a260fadf6ef738919f4d08 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Tue, 23 Sep 2025 22:45:53 +0300 Subject: [PATCH 64/68] Add image to display request lifecycle --- assets/images/request_lifecycle.png | Bin 0 -> 121519 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/request_lifecycle.png diff --git a/assets/images/request_lifecycle.png b/assets/images/request_lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..6d31ecf8afad31f4e2978ea1b813d92653f1d7b0 GIT binary patch literal 121519 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Ʃt+x-E$g zlSNAoYuEDd5Zt`4u8xFq=M2G0aCzTr`wwVA6VBv|^M~sT*0F9VA`_>B8w&C@A95)AJ(+@?t~ibV-}( z8M}AqL1E+%4jhC!jvmk7myJ=5PEIysp1%vuPB}a=9NyXYGR)D)eZvbnZY!)5cSRd;G{cKxI6?ELPNf!4OeVbW73KYEsE zY8j~0qzhV(A^h-IFg){diMOX3ET`K$y_=)SSk$scE!K$ppZ<0G(9qu;VuD)g9m+~L z2be#s+OimnVAFBk^nuf9tOVu&M#{p+2_Xv{QgCi0-<~QrY})n)_$2HIH~)Nt%{+GS z$XwjGZIp5dX9HhF!-69-2HodjFziuhA`_Ce{0dG}(SJs5q@1Pg zVFsk>%eAk(?Bdyn?1e%hwMR7Laf0T2vn0Ff4$94lI{gcqpH$G3>W8nL82w<%o$pBm zrEZ|3bi3Cep!z$FSU5(JyTdceC?YucEfDOdPi3*QyGuG_G48;61*%2V$}brS^^N@Z zIx8mA3cLxXGox0+?_V&6^=ki9ILOvCP$<}@e;%){9*UWY?dz?(9ACgT@RL?@$XBdL zCB%d;t-BY#>E+8O1wqd)Z7}?@)MVEFh8ZjE^fqpMmXSO;w!_*USGSzp@M`VNE)758 z-ro7pvQ>F>^FyDs@kra~`d8muTQ)g-Qb3m;F;1C_uP*E?CU${nFk8Fq3(pwROhKLt zLx!vmiA+pRM#8!n^B6oNDWmD@jvew(Rsj;Yl%CH1hyG4i*RxAl(3sKA`?5?RQ1d3s zT5g1f4wC;ruHFM2>-LQszuT!KvnUjjnKD8d$t_vgyJ*O&h!U9%WreKl5!p!+QbuK^ zWJ^k9WoPg8|9p9V|Mxv!N5}JYJWt*CcU;$bo#*)(Cljm@5V!9LB*w?vqEQNH3ezB< z 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 UqeIhas Date: Tue, 23 Sep 2025 22:45:57 +0300 Subject: [PATCH 65/68] Update README.md --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index be9788f..6318492 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Pytest](https://github.com/pypa/hatch/actions/workflows/test.yml/badge.svg) +![Pytest](https://github.com/pypa/hatch/actions/workflows/protected.yml/badge.svg) ![Pytest Coverage](https://github.com/srg12354/fastapi-auth-api/blob/coverage-badge/coverage.svg) [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/) [![Linter Ruff](https://img.shields.io/badge/Linter-Ruff-brightgreen)](https://github.com/charliermarsh/ruff) @@ -7,6 +7,8 @@ ## About Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/), this project represents a simple cookies-based role-based authentication service using JWT tokens, built with FastAPI and managed by Nginx's [Auth Sub Request](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) module to verify users access to the resources of the API. +Request Flow + ## Features - Access and refresh JWT tokens stored in secure cookies @@ -18,15 +20,15 @@ Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT token ## How to protect an endpoint -Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with a specific role. For example, let's say you have an endpoint `/test` that you want to protect it from the public access: +Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with a specific role. For example, let's say you have an endpoint `/protected` that you want to protect it from the public access: -### Step 1. Add a location block for the target to [default.conf.tpl](proxy/default.conf.tpl) Nginx configuration file: +### Step 1. Add a location block for the target to [Nginx config](proxy/default.conf.tpl): ```nginx - location = /test { + location = /protected { include /etc/nginx/snippets/auth_subrequest.conf; - proxy_pass http://auth_api/test; + proxy_pass http://auth_api/protected; } ``` @@ -35,7 +37,7 @@ The most important part in the location block the inclusion of the `auth_subrequ ### Step 2. Grant access to the endpoint only to users with a specific role. -For this, you simple need to insert the endpoint into the `locations` attribute of the specified role in [policy.json](src/policy.json) file. For example, let's say that access to the `/test` endpoint should be granted only to users with the `moderator` role: +For this, you simple need to insert the endpoint into the `locations` attribute of the specified role in [policy.json](src/policy.json) file. For example, let's say that access to the `/protected` endpoint should be granted only to users with the `moderator` role: ```json "moderator": { @@ -43,12 +45,12 @@ For this, you simple need to insert the endpoint into the `locations` attribute "/docs", "/login", "/logout", - "/test" + "/protected" ] } ``` -As a result, after authentication and authorization process, Nginx will automatically send an internal subrequest to the API for each request to the `/test` endpoint, verifying user's JWT token and role. If the user doesn't have access to thea protected endpoint, the API will respond with a `403 Forbidden` status code. +As a result, after authentication and authorization process, Nginx will automatically send an internal subrequest to the API for each request to the `/protected` endpoint, verifying user's JWT token and role. If the user doesn't have access to thea protected endpoint, the API will respond with a `403 Forbidden` status code. ## System Requirements @@ -106,4 +108,5 @@ After creating the user, navigate to `http://localhost/` to access the Swagger U - [OAuth2 with Password (and hashing), Bearer with JWT tokens¶](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/) - [FastAPI JWT Auth](https://indominusbyte.github.io/fastapi-jwt-auth/) - [Nginx. Authentication Based on Subrequest Result](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) - + - [JSON Web Token (JWT) Debugger](https://www.jwt.io/) + \ No newline at end of file From 1256e287f162e869b65dbef89764869ac30c8911 Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Wed, 24 Sep 2025 12:57:03 +0300 Subject: [PATCH 66/68] Update request_lifecycle.png --- assets/images/request_lifecycle.png | Bin 121519 -> 120723 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/images/request_lifecycle.png b/assets/images/request_lifecycle.png index 6d31ecf8afad31f4e2978ea1b813d92653f1d7b0..b29fb4243961c9f9899a16a7ab4e382f6df5dc7d 100644 GIT binary patch literal 120723 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\YI6MFr64L24a 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Ʃt+x-E$g zlSNAoYuEDd5Zt`4u8xFq=M2G0aCzTr`wwVA6VBv|^M~sT*0F9VA`_>B8w&C@A95)AJ(+@?t~ibV-}( z8M}AqL1E+%4jhC!jvmk7myJ=5PEIysp1%vuPB}a=9NyXYGR)D)eZvbnZY!)5cSRd;G{cKxI6?ELPNf!4OeVbW73KYEsE zY8j~0qzhV(A^h-IFg){diMOX3ET`K$y_=)SSk$scE!K$ppZ<0G(9qu;VuD)g9m+~L z2be#s+OimnVAFBk^nuf9tOVu&M#{p+2_Xv{QgCi0-<~QrY})n)_$2HIH~)Nt%{+GS z$XwjGZIp5dX9HhF!-69-2HodjFziuhA`_Ce{0dG}(SJs5q@1Pg zVFsk>%eAk(?Bdyn?1e%hwMR7Laf0T2vn0Ff4$94lI{gcqpH$G3>W8nL82w<%o$pBm zrEZ|3bi3Cep!z$FSU5(JyTdceC?YucEfDOdPi3*QyGuG_G48;61*%2V$}brS^^N@Z zIx8mA3cLxXGox0+?_V&6^=ki9ILOvCP$<}@e;%){9*UWY?dz?(9ACgT@RL?@$XBdL zCB%d;t-BY#>E+8O1wqd)Z7}?@)MVEFh8ZjE^fqpMmXSO;w!_*USGSzp@M`VNE)758 z-ro7pvQ>F>^FyDs@kra~`d8muTQ)g-Qb3m;F;1C_uP*E?CU${nFk8Fq3(pwROhKLt zLx!vmiA+pRM#8!n^B6oNDWmD@jvew(Rsj;Yl%CH1hyG4i*RxAl(3sKA`?5?RQ1d3s zT5g1f4wC;ruHFM2>-LQszuT!KvnUjjnKD8d$t_vgyJ*O&h!U9%WreKl5!p!+QbuK^ zWJ^k9WoPg8|9p9V|Mxv!N5}JYJWt*CcU;$bo#*)(Cljm@5V!9LB*w?vqEQNH3ezB< z z2uKKL4cJNe`NR7N0tRO`nCs5;(=-~-?Fa)pMiVDt(33V%(#Ju5LU_>MaU`>`BkW(IrH zv}*t5%QmI+B81bSAz>-bV%3c0jNuI++~8@pO|m4UrTqjc6Yvije}M9vRnJs`700tP z9PU&mj8ZyO72>b~Hhg7!S_<(4NI0+(_IOD5#!P458Uj1df7D+Ht_YuD?&II+5Blb9 zd$N#q1Al6Li6K~AXg^Wszk6rPScDS~6iRA!&_VO-PquxA@w6^ch7$1i^bjL8POn%> z>W!<9=dL#qb?%n)i-%jaa~d0CEj@q!WyU9u!Yq-4A8Xh`aib1%mY{S2p{CKA!>n_ z3Y>%8`qJnL)FQZf``=XIM$P}0u({Ev0l-iR(a4`oK=prgB6C|W3IfAB&UKJCfTNG0 zfpQ_G>(aE=37MP7m% zLeB=d9Dd-~TJ48{&P|3Kp9C)|##Tr_9WB^|-qIHabWSu*J$eBS4LWho;DO@radRse z8b(^n0^zo#8AWNK|Fo{OlsMQ6Jbl;hF|79w zH}X3m-xr4!9(EWh{Bv2uDoFIJTzB7+<~sLmo+RaGshVZX8$UFkI_n2^akU=}Oyhf0;w3@cJQ&nWfgOf`ZnjrYV$*JJNsnYbyi9 zODhsbmdcN;h4WJ6yLahp!A70{wJOE#O0a6O#W`|>~JZ0(vU4K!#rZ3;*}bjPWo*I!nyBjN_~^QJ9>8e0Q=5*h_LT@ zy|oUf0;<6xVe07MU|~3#!`!08}gBjMqPh_GjG}AfkcHZ0`7MROT7Qob#zR9(CG)guW(>@Z=fGm)u(B#O;xkI z{>KG~$m7j?vv6$f%J&m05lXTJ_rr&1RV-NRIc@kaLULR`gLv-&l6n~r*f=Rrl*BnCBh?p*jqQN%E%nR>cB)rWc4gxWz2s5ZfjebRur7 zKl$Jj<$#h>!bETXkp8x9@ioj_R*cW>TWe+XRtsCusDz!2KJLZd`X~+TEWYdT>xH;p zKY0ejxX~%$K&Bs#K&I2G2HL&4O)FF@HVuE>LjvGS|L*Ch7i4T=uu1?oB*e#qZgFL! z-2-2Uq8KZX?l+orvt*)Ow1kNjWDc;lKw3Y1pduOU4m5kwc?Zmg#7?{B4Fobz`2h*A z-;iC6DBgWOH@arD#snnSBKM6zf`CgZ63@@?9#rDhQ5$J{o~e4XH}L)Ylu67O#e-)) zPPT4`9~%G>Usiv3TsI0*Qrg~newXAp<9B-`X?bZ=UXSx~)c2Q(ARH*|Hpug1V;_{P z-K;Qk)2AE9|M#iFw_dlvUjmJfUKBFYvw9czH9KouurR<3b)3dmxvL0!87KyWJi%Oq z?qSovj-Qt!nl<{(K|_F*6&`?BAJRin*wf}}qRU@e>P`r+1dNOLZlHzf1zoAJj1js+ zKRVH2T_1Vf(aA}d3D&lJhJEoM0yU8*#bhNVX?Srzi{#*|Tc;hN**MABI z;@%JtY89&=I|78&%m*iC(^Mha(Jim?0GxX-v0$tM91F}|a`NljbrUjBy$3&5x?mb^;)8{$t^5cu9sx=@pSWs{7A+yhHMw{(%d&~5E)t1uB?A$_ ze-zxp0fwe;p`TwhwqiaZoduS=&ga9^Fw#cQD{uxtCA3`QUkAu@6 zUKV+6Qq<5#!A4B5WED&MRL9u_F;Z0J(LROR`Had`s118-N9x)41U z$Sa^2>ez`1354pmGVmbzd3?O!#ZMdmJwfLSw7DOI?~tpjYwI(tRiw}3XcnB4(hDPc z1e<1x)~{_8?;{={!CC5`-MMK?P^Q2gN1ar+^Vy~*7MMOQ>@1cxNs2pg>7nD*W{n$1 zkm2nX_wdmN84D6v zENGk^U;GpP46%R+V z3#cSeiNfXV7&TfNdlKLd5vgR4)-(u1@L0iF#I$o5{NL zG46t}v`oJOPYy`om61K($;6<5dPF7^p2>-cII51cpw zXf#GrJ#h=k_pbX!fI8uf!)+=xF#+#SfQSL{%8$9uP1jq`>=g}sc^Gnz+qZ>p?05M$ zK5J)*Cyzn`GrZA^r(+`aq206fnN);&-%&Wfl!raRSy&d$>E@@klOLbES>MsU*UPn5Hn+BN# zKWK)DxUqpQj+2O(&mNxt?bKs+5HscR@Tt{7l}=xQ1ho=>0afO} zpFbaNqcfnh<3mG3(3NG3J2<~&MzQ+1M=508A=wT?3q#RB|O)WzvhF2`@ zo!hf#&kGDM_k4WEbT4lF^%h`t>2~h{nc;ClaEUYm?(8|}qXE%Ce%ty8aSK{ZJQt1A zryun!?g0}B9gOQOuL&n)gqpQo03L>AeG_&!4Xrw?R zaccIY{c_hdimCc09WuzcSMz!m)q0Y7?OS~pDwf)Z+o9t4vpMF@9Z-jij5v6e9&Bm& z9AI*FA;ft>2ATsTAn4r#w)K7gegR~_GG=OFDRicW_ND-nqOs>&y>AjFGa-22Plx=9 zq~(K+9QMSEV)%y)Hgf3hJC60g-)m)EJ*`fCzW8i&X2aqfGL_t1RA6{4w=OS_Ipes1 zB9ZO^z{RLDJfzi5Td z{FUExv!+L{{e07pgO_j%!N@*<9so1cTd8FdkS!lW+oHZ+1FaGSO0WNY{b|xo;w1j5 zB1TV_;^rpYvB)%9-GR6anh>3}1PPA!|{;{RyPMJ47`4&*?RXFp%8z-jPn?tQy(&4&n7h`P;p5Ay+Et-RG*y&iVz8#3 zJzHNsMP2tbbaMRQ(%c~hr64bgmKKr8c?;|&G)M@_MI91J`Nw+>{PT=^27{Ou6jR8g z&`09?r?>X)Oq9e*c3x6cpEjV8@sfdY) zSbQJ}cWfk?$Hhpp3kU!O|8NpoE|cv0>lyb3<%{d<*WF#m!D}dRU@b2p-4!FstJ*#r z6){Q4{x(TqtR}_rY*T|CoKbL&P`G_;Zm!OEFIH|@saBD(b_jZT2V~7hFAV&ocWe5O zM&f*(XyBN?0Cp>CRa@a7G9oI>L7b{=+qVY>r7sZc)Jkh+WaaNSi`xA-Y{7e13Vr8~ zLt^!3fQ7gXUkWU!c{|6Mm*Y$4XrVTQ^x$6?8bgj+dN9esp)G1%pCGSgU@*t+=qT(k zn!NHRXHbevG9N|v1H7ce|F}qhXfbXX1Xa%ygvo&UzSRrsTZ@C^@fH4c?zAM*#?N2k z?~=wRu!{Oygwnp$%szS~>~I9Nv`x%erAz8<8bEiKPlAZ^|HBzQ74$c%pF$^kYPmx1=! zs&5v3YV{52zeY(Xhj2sYYQ$ZU?xsP8MmkxwZJU(K^uVCuFlSVuW0R66iL_GD=Hek@ zS7<|WquuJ1AV}UtR+HKHRz|Lj-!s>8&`A52W%~6Do$JvV-wiUpTMn|=>GI$2JUK|x z+DILc_&Lybh*{9pt#$F#j(ZzTCYB_mq(o<8oc<|Pu^(u6YTLkn-{Ce4>Vu;*YBBMv zdx$Th4K&!At_^p~_lJy5#n#0plv|XJk=`8ZN$l}gm1K}6eS9773$FZ$P>yoHKOAoEK$4Q+q7!tpzSo}U|o&z(bA7Zr`I`D1AF-V^v( z;%C?dF6`s z9UaAO(NZeOsKPyZ^wVhDDvKGvDR+J8GY%lk97~3zGTR6FlRB-K&4|`$@rJA3p~EpFq-VY?`9! z_bP!im$>lwG1{W)Kbzl++K5pey|#88cb{&fU?rWrURoZAqt_`Ymem`F^2xzVSCw*e zCo2z;$=uxCz4F*Gz4A4&wEnt1`u+X4<^<0dDU+W+lW|$7kCVY6IyIs?{8Oa%^I9rF zGT6a^IN`0Rnv7ZHn(hrvA(K;sZun0m%Ie18q_c^snqHpX-dr5y;OV(Jbt|h7Gy2^P zg-*9dV)~wWd6`?qCa_NpE2X7T9<}}NI!!5w>$LOs;ocv&SxzkHjrj+XtsRI77(XEM z2%L{vVRK8As^TXa(zHu^HMN4dhMh+5c6OzoI4Tly_*~rf-JYJp!YR4}wzoLU%$Nir zdSo)``|oXhqr}@FF@7pR7g4H_150nXL@@0@VmjmXn+n4k(Fi=pL^Cz zxw&nlIB+lS3$2e2#KntCcrDOz(W?LK?RM{X`R_yNiI?K(>2t%;NmJ2#ak`T&id`N( z~ z97j)1J_&EDShTTa+wkDg9*MPUJV|@cor+|@eDCh5Q{+?@;@y*k*=&y1v}AjsMZ?)Bu=fTyRaSe~G2zN2oL zwm8l833W#8hg-ME($0)ngKt#-4Y(+G{m>(}@frQ_Yu9)fMhAa}{1|d%{oe)2oN=69irQF zrY43u)bMxyojdVVn;%aPr>e3JG{|e!XP7pDrUYAo@H4@Y2ym_aBXZqYc@g7ui_B~s+ ze6_YVKU3lnl+NqRqQby1C5^>PN`XNYBGB#I@gFi@J&BQ|-Q@c5BOUdD-pNKS|9f40af*jX!a~kc@EuW{`qv(+8RFv^90FgFx?7UFcWacp5wV&-;wgjpiJKmX7ojsWY$%@0KDw4r+%;WiY`5X$#FGfcJga~_(ceYBtIs%&i@&upcq`z(H*XEgiOetG$U-;s_}DCR30-?;v76{&?7a5{4`Jf;2X ztdS?XF6B^SS=k}`YZiFE*Bjq#HH$nZd~MI_8ohgsj{)VUFXgwe13!ZTHPmZ__Mcon zPo|&=4mEfqYK^nq3T$jwuDs4V zATN)c-s(5vv7qpa_OZ2xd2*20i%h<6wHk9S8^WBrrMJTRjAxE&4upWZ(wckY~(_%Gqh@gP1@3%hB0MAgcw zdqAhfJyOF{yIlSx_x5T$BgH*>uITu;Tg|~i?(c4Tx%Ky= zSjNZO^vhKhy!iNdo{nW_vnv5{7#QPTdu%5s9^%>tlrA2SbR-)YA^W$n8UK}cUFq;) zTqY1ykDY7l`tG}gOx5aYq%V$oYA4YN4w?&j`lbI~P4W*tZ{LGh*DR8fb)_=O z_ZcK|hZPt)3he#AeC>IotI2L=R(GqgtJDNLJsMiF>3P~{wYux8^c4mN`Q^n+@4hf#k{jW68^_LNiK0uDXGk$*bPL|Pg z)aqw<&MgELyOptMYY#N`M^|Mk?XGWp(PdVr5fZBDu~Yaa=hEnge<^DlYYs2}&Fagy zQgba@oW?Ci?#+LPmecbz10sXRGCi%~@^|-MrH&3d`b6`;6^)yGJsr%Fy(&+H#}Wkm zrXEf6qk(}bZ^L%(i@RI_-P8QClc3Pmp`)wI;3VxMcdEE6G`l+rT+ct-q>GJqNO`|z z!#pc1@$`fhLWGL%uLr)qTY&3$M0`A#U|?sbXNBgQl142>6&ExAd+Uh9_I)58JptQ1 zNj%7%H{Y&O#`-h=-0>TC{aBMXZ3n$cP}I8d;@UTAHVwG>tT&elV~au%8df=QU^Qz& zx9Fr{NoIKbT-EI zg6?+S_l?&rjp?CWO|sNyi91W#v{PQ| zT0>LLB{Riskk$U3JG$4)i-V4*@!}ovvT|>!@801N8li_xt}5Z~d@0ewc_^n8Gq9Ug z;PN4%*pWVUkgLAGcy>0L$x(~=KvJzqPOGs$$>2?uG9Y5;<0?Gb3If{OeuM!@*44%7 zOdIsyV|^o-lw@M5SmL2O)M4A-lD|7`gA#tW7`AO8PG%H~SD-kjM%!g10Qd2I9UwJjHys;N%& z@UxxF%oM+z2sggQS60^KkQU34`olkU!s4QgqN3e2nd!6$1*`0SBh+q2x5h=;P|W)s6W=`i4Hs|7xEp zG``!ljXteVfOFTAJW*73K!xU~FbSd1cMaoTT?Hy~3KVk-K1qX;E1SS&b|Rf!(6qMWjkTc)kkgq6QM)+{^K3*>@!ik z@$*}BpRu(|q-I|Wd8Cu2wNG|XCJOPVwzzh5Om=SS>1m4l6-cvfd%~+D{Y&%n^j8FLYIDRw4d3fk?o zG>wJn`m9)AN(AD#-s1|hdzP00=822fmHP@`B1hY;?M@X`fp_OoFRw-dSJ(d{L9JMK zIWCKiS|-y7Mv>nf5Az18_XeefjhYclR`7UgiMxNti0aFiz>m{)0wN8W?bo^oD(mop z??PkF&Wr#{{J(2_UETWT%+({aAPHr@GDF*1k zJeJ!k1nIjkWb=11_J+LRo`&}qoD_Vpo1I{VHNwX1QM1jEEosW^zyovfvsEr`?m|&eb7UUxLKG7Ktgi|SR4L_Eu@IPk_@D~_U0A= z4>pL4of!!=X!D<=WmR1-^KHI&Y{2rlHo{NqZ`3pdl)bo1eDw@DneE}8=_j{&%eGkw zHo^WMm2uGl-Ot=Z10Lp!lb<&yop<6LEN5hG+=K(v+FDT|pxx-4`%N^68;A_&j+s+9h@bTjjo-myqoE|`D+9bP0w9{N5CMQ`0)0~pE7|UmBg>F}>-3vmg#R%ty|)f zKW((NKcw_^Gb0=6jU!aTMg_ia9P#6io%V8qO@0Za%;&a^Xh%`J-ZF^ z15f)}_NB`zy>=S<_qM`@++9#RS2Fxoo>-P~G6y<>GPtCR_lv&Ye<;$kG3U*%Tv1JJ zP3s{U8Clw`L?dWG(qX@wFC|jyyPSme12AJghCvv&K#4?o`#gpk0@@&`Q*gBbffn8x zKtMpkCqJMhMLdKa;c)1$9|<^0<+W92PdGez%74{;tiVC$=HDk78SCn<#cPr&Lxq!y z6$Q>@S}C#{hG!&YBsJ+MB}boq;Jb|}`mTVd%N|}=bCD0;8GLRr7R*J_isOobC4u4B zgRD3sHb-oGOxPL5)$Q*{-%q!d<@Y;=$=DH}3Ri2*_SC}9m$H3N=uM(O{_0nE_0B@e z=Hdvzy}3K7Q`1E2UcEXZEpUJ*N{{b-(T2TFe7@ZLzAO^^!7>_;Z`7I_CJwQPhqbk} z!2D_Jz*QgyAB=8zm6x~j=K&RzwkqT%4(IjvFo9qO${6TK!TN#FAJaLY9=u^^mvL^B z7T@8+)eQ|$a0#=sKls`MZx=&-{e1XbF$$g5&+lxg#)kuFRwe_ojF56U?q4y14;mX& zID7W*7-6!NAnzXoi8-`$+Pgb#^Rd3c{KXJQIIPyz)g2WTh2d2n+%e#>%#*h=lwAXF zEkZ19Tzr-cPYS@BF0io$Q4v2G8N={W+p=SiT2l`c>9CsuVGcOb!&A?Sid+Ga!(P7u z?Ax@nV41c6)P!6cJWq5}02X`fU?hkekj15)O*O*M1gt;UcB|{@VL%-*QOI9vo?v&` zsw%6h7Gpd&OeDa1g1Z>F1OTA{4;{7bK9kBA1)LLte!bJDH&5IEpbv`{Dr#Sl%Yo$w z@rX@bK6L9j#ztHTa88H6GA-wXkFwd+3Ug| z<@XJ@TWyJ<(0ZQe7vPuSGaQ%yfNw$6)*`sGwKI4(cPZP|WX?BzryKTZXFP1*DlIYb zYPMsMc7mBx%V6ns;Bn^@2R#ms*tLJX=4Shs_I=%L?_0$AQ>*u6)N9uGz3uIq>EKzt zaQTPpWh_s%HU)WP#z?eY9epJ?n3oG(asR=hpL4xVRN1b-i*3h7pW0nJn7F;en#qZe zGJ`=*?EE=6m7eyE_ha6B$(=+Rk>#Ruoo+FcU{Qrt7QsIec?=?v&|Yrs7ndNhD}i(l z8m&{7D}dTz;ssTtmUs(ZzcD6hfxo?R{k_I#fS7YYGY7c3`%nbfJF83zsDQwc1gZ~9 zQiT{>=mbHnaBy@q(9_dmX#-gW;QLe9PUN`ZbpvK6rmjF!{(L9&BXAe?r+9;v1I5E6 zOq(hsv8U+p?u<)fifL}htI!$8Kx~k6@vSD z53CjNqt3q!M-WU_z^Iy|nd&+lC5ACQKe40-v|V(4PVWB!4HcBaUwFQ`fl=iI-zEHl z<05YxBP*-GoHFEB`0>Q#7v=F*giA<1g(1ZLOACL9Gd<0$q&WREXUvDFcTw9NIIb`$ z`?-fJ`Oet2EB&!)z6YfxS1R)lsa8w&OXkl68z-j>9{ahpPp0?5;f$hEol7HCtu1<& zQ(jp3rF~!YpH-6DA;u7Q=2P<9q>0-^rJ910&9jDAdc0wNFxg+>U!%yi%a$z)y6sOq zZQD#s<>i7y=!85@u`JF^Jy&lZlB&I|#;$xfMYqF$1C!HM%56xm1bG#7d`xUDX=%|P ze*qmn=6wcxV*UkO@G>$o!0ZCn42+u^d>xD_fZG>V!LMJxl#E2BrQI-w2!Yto&kw># z(IZD3=Ef6Yw*tln2CBgQ6#qmJ?0$GQMuH$7amw$*@Y6DW(R!S-`fkf_&$g@ z@w!3%Rg5XC+E2Ib;qvk_7{U8?_9pmCz0S>rYMw9;2m6_t8Pgy^8-ay>u=6vBH9@5E zN}%kz4BktC)(D=XvJg?GSP2q^Gfpv#ofHUp!^D3pDytg7Uy#t%E0kAsp7WgG@U zzz?B1Qd9s&MCqAD8jJ98Fn0U>#)39`_}@RNTYYNaqJV-5i!N1Ae86+kj-LG{+CBs= z&7F^U6qptl_wpsULxf$TySqE0Ac*ytlz{d2$bxXm#v+4Xis=C1o(2Z10$GRVsvhVr zXTt^Mxaepw>jWLvY(zy+ARz!jv;@T){5j{|YUb3uEu@p#wZb$~r+a2Mx+m&K7@7kC*?rOxCc_jVF+#OpZ1D5RG{={T`3jNrnh43`f5^bhXx-QlR zuI3CQ#G_%8GBdfA-6~|9XIJE|6bKqky4B$iKVo>aZMybx+iEdt7oT&;`P8!;72{?1 zikibc5MQ4CWA%#^X@8ww*qt%ibIz*PO|)j@cla!fDZRIk=eZoSFUi`TE6IFi=GP92 zho3y- z(`|+4~=}mzV1g z2l@lk)+(hCDP{zUW-)ot0Ryy@Z*jbwfZ(SR(l~ewV9Oh}B5V6uRt9bxKg`YZ+FcF7 z(VSH~b?Q@ctSis1tv{|!Bc{QG?tm1b-)EI@xB<^AXDK&O3h{|SRcd9rQSj4*kO20K zUS3|aefI4x70RiLUH;M>v_)8pN5|SgenG~?r7r>loYjBfz1fo z$rp(+oSG0lak7E_U~JqDA%hPUwtx%n7DqSsAWptrs)y`u5I>&&{$|VJ^YBu|g;Yw- zIWNQSq_WfrUq69ozCB;3oP^4h(kL)oJOS0`!0x$Qpo9I^eBpoa--a+Z-;}8%X!x^kAGJf51TZ4FzjCTkfJ!hdSJu^)(-9(_S z(cy_!(W7-8_tsGr2=w-v&<3b+nY`OZwd0HfW9sO{RfeKN-nn_AQO85h#=JP(CM5ja zq5(d+if7`QGy5}(f!vikk!sH^LGZ7vU4?W+;jm)!hp?t8PtR|rs+eXq(8ubw2nyd> zvh?EU1CxRZ49xC;n$Zgm9{=#~&LzC|i^t$tdzgNNy6*LrulTJLv}={A7aoJXiC}!D z+=+|;^8RLW%BLpK$T_HnF@GZ^#nV%2VMwUb!GEmsP%Jn>4?V%G0H>Ah&Y4B8P9%C7 zV=UAz*a}qxE`*oWyct};ykqY-#@~hf6ud81c+Md~E6d&i66na_pb{C0Byu~CMsA#^ zP)JfyV^+LRY}`3;k-7Q!IO5@0qCi}YxjFQejFt7#fy=qrqre6g-mGGC8Rr}sc~@#Ng=>xb%o3%PnMdf)Ueqx`b%>!rm@#4lFu7jM;d zw4UyJ8pRM&E)cqu;O#lyFcDLt3OM_k0=k?XmM47Tt!SSGyz^>TRPAorkk_%G+i~r zOCc@a>+6=r9w%qrsj&RX%YBo*^!&6rzg4Ym4djriuQ4VqGh&;IoMKV_WuxBDaW zM$#@rau-N>Z?p%a$|`_AC*VVktK&%u|3nZ zs*4?law-`$uhd!H<}xfQ?2Gy&-kVhOj~kH35C6&QrcK%wj0-ztq&M^n@CFrr-s_NYYpE5dt+hSDE_RkLCj2T==*i2U_Ye z5b*W`%<6&9SLIMQoao@ca)dfs+Re2Z)A-KXd=8F`G5`?^x_h@F&vD$Veyn<=`rP%oub^Dk|iit$GsHs-w5Ct?rS9 zKh3#U7clTV!q4w)PXZ={!50vuvP|RV3)M1+nlO3BxF?Vz6Rk=Jl&%L&51|LdL&alO*J?-Bt$g{qGuvt;pV@|& zO*Fsr5T8$(;J-^lfBya%PgUx9*y5Z!@9y}}n|0f!Q-8h5jn}pEA556 z>*$L=i~dM)KXvg#-Q6u)Sf9)3w#r_tB1(LqgGIm&)WHHFr2D`uMn>&+6_k{1rmgW0JIBB<3>HzWt_72CL{z*PhL3mhtVf zVN>f>hg%){Ka6Nxf18-NPD(ds?)UFoi>zO*rtQSW|8_Cdtxr8qwB-1I;oTr*@;a0D z>SFX>81MA7p4q>vdsR6bvf{>)kGZeb#R~0zF=*vuw*(X+*#IiDEmm(@H9hSXS9qrfw?M;| zkAxI-QTXs-LdL(JIq6lVfpAm`^F`qI-~Eo2UH3Iaz7R*W0)n74MDGjIF6lEnY3nZbi$T^FDID3|T)!_Na=W{t?t+!MF^kC{J?yniU| zs^z!E`S*)a*OIenDSZ>I%QJIq58Ictre?-?9HAOsI6-L>dF`5&dUa2^+#{^8kf(cU zcm<>jt}sV_V-a$B+1Nj{;`XePpMPm&JYQ*xt8PQ>K8cHgb~737-deu9&2DPayL^DbPKtQI%8Zdg@`_% z4hl$<+|+XmT3_GfF`LOs`bd4bmDP_+r^7w+sfXWm6{|G26A|t={ z*oI4$-7#@#qy$G#cPe-kLy0B|goOIi8 z6bp^Tq&-b@b#k#fsw?RHuCO<^d9#42VKX_eebN=axM}?<^rrOXIO3(I!VIhUtNE9& z{xnvS3^ka}qr;?_iBk3U$KG%0ZS1~kvt8oNDlVE3xp_uKeo|Iq!tU?CtoPwJ{4z>! zM|GNu{yh6WLNcKB>{^bBp{%{sN-B6F3_YD^FN^aa5IZ3pYi_7`r&n@DQ4ILO1pEFo75xta40 zQY)Q1J24!4ChT|+F|oDNM<9*qf!KKEw{L~^BQdR4p?^7wkcNDeuqs3Kg{J_~Eb`1M z8b`$4SPaI5(-x1%b&jJw>4G-yI%;bBi}qzdOHS7PmV%=m2QzPrQDA3K6X8_01p5ub zw-X*?g!2u4U>6xw(g7ex1-^c;{rI_3yd5s9C(ku$e$-X9+B7aL#28_h z0Oo#vYi-_X7$_7i&ksUC7OI=|@(AgqkhTX!YykDtFmGC6|B&+hyxsSk30({~!zCVh za%{JF9n2$Gu;BJvx06d{-lqP8_=XU%k*a=G-!p~NT_UExOjEw0!FNzebNa?$$9U@3 z5n9(8mCBx*bIGAGM#IGh3UZAP?&YtePCTu{XVqM^aKe6g>+F5a*qBTAg+;FCl=bdb z+U!1kdDz4C_^tNmF8u-x`DLv_U#njf0LxyimgkfTb(^1PP%be0dm*=PkuCZ*y|hWi zOGYQ>k7J{Q@NjHzTiKT^mJ4MYDM8Watdqmyf$ZPcP4ear{e@e*6wNKh?^iy@AJ|=7 z*yq+*e0H0i_n}cnyJG`C@)G)Z*x&Ur4gO8+LFJB6Oe?CC*?I1&cHycwMMU)Xs8LB8 z>t8eWmZbr^)U9+tWP4CG-RIw{^|BH<$dN}mxT_8^4gu@v~krxL>(Lu0Is|!EQXht7p$$V zN6;5PB1x=dv_>K6kht-Ol>I7xwh8mE@uk_VF~h?>x&u1h+`8HahafXSLrVFy`K3#O zsMg@h4*Mpww03%wbliZx`(X7gdSt&ROg=yH^xVp(p{olW-+d@VAsqMf+u6`d;8)P| zFilaAYRLtvpOWKG6*|JuQTPIhau3YP$5kzoXkt0N^M2{+H0}wBIyt>Ka5Hbps(O1g zZ>;OpX8t6x0+C^0K39uz*$+tiqiN1H`@7; zPI0NsD9>Q=DDK$ICoQM7Ra`zrsqVDamnW}lE@XJ|`%~(rV;}nbO)L3WEo-Yk|0uX@ za7ltXk+Za^>CZY+oCWNiAh0~hhOtF^BY(c&nplQQ3%pM2AjpEd1u{45me+EoLSxsC zV4Mq@Ce59vP#VDT1-)fi*tH`(K|D8y!|^YyXhK4kFw%C_yHP1ufWRQqF}KN7Rw7(g zqM);LU_cE07KI!)w+EYHIcHvr;~u__SOLH|(9y(*bsTt7==;HF0RySWdS;!Mi7wL} z&MW+$LXDP2DQP+da1x+K{|6m$EOH&)&?1o{h6V*l0ZdgTLK5er2+SkeUqLna8hRCKXgD|An{3;T@8$?e+!RA$ za_pV&5U0=$jV2A|>6TaBqb4dYDwhqr9x-bF5UKMq6)ndZ`FQGt-nFM+lMkB}OQe5x zNE_>J4SF3f$hQ6T{L%k$0WR@ommNILxMVhIMpG0&6kp5x%ujRQ6?=7A?}D|k^6>kK zkktLUwXJIF6*)_VJ-U@fg^fZ0x<}7VyL1CJM zZe^S1au~UADzdh+_R%FE+3>%+Wm369M<+r$DP zN*^9gNArdFOziPP92}S!w()5b}s4^mGprDRsU@$wux{mV<~s-mKEgq{&+ zgi$-{Ld%y|PP9I4*;V5jr1XKdV5h^kYr3r4e)={YornjwR`?CnCbUuUu{`p^3~1w|D9$kj9) z8-F)rBjo>W``D7nuM=jwM@EvAuKjp zjyzUJeSTpww56&b`?|Jd%8J|ZQ*$fn)({&i*+H#rT2fY)NbOOF=!>T|@+0d(MHj!` zt3R;*RaC5RFS6^emxD(S#%;kx6p&0*N+FeqNDhXCa*K)0A|*f>4-f*qSz1;~DyrSv z9tsXBpO%;RkuSRj_ono;G&rl2m6v04SDq^o5EKkdz19M(;doSuZn&3MB{~l<&PL*j z9`fhj2_PQ(n3-V>be0U80p!{aE9qR^+|c`Z-QmVgCos+YZ^#^YM=<36VD`}qA4rv# z(k=ljrQf)`Ny-+p>;>9nB}%_nE2v=u9nI>?|(%qK4-f^ytY~8Wv50) zx(}FWbsoEZTwA|FqnWI$BA+2#{DpDFwoJ-f#dlW*=MT4ck2A$KSMB@25r+b=A-*9k z_LH8+e+AjIp5^%c{toBEQ6unJiQIi1c*&+!&p?ZgWsjQ5Wq#)KoE=JDPW;*T0s~*a zOVZI!_TjZZdX)|@csr-j)2fdjpV`R2M#X_XI3{Cp;;jK=Av`S-nO?OFflXoj?v?@< z9RWVuZeT+^pBqTegiu-1v}zCBXW3}BZo~oYJU^Lw@7_lkwVN2d!%2<*#Nkyoum?(W zz(v*7^-5Dv_Toj;1VB}uktRKeS z{=MVNB%&x7Lf24R`|9=UZ98`|Y~85*2}PF}XU7Rx{Jybx-Tarho);MzfO|48Z2SRd zCeDuccP31G+%r>BK37yEkL!1qZNI{>oqpRx7>0*9S){6RH_a%JA%;yWy30d48C2Gz zVZcNkKve6VmexD>oV=dSH&kCw*UJDPIQ@(gg6t+YffUp75Z5g(;R_S><6)GPlD=Pf zM++Ms=*v-UV%+4-;e3btPUmoI_qp&f8rgmM)as_nNn%PtLo&^J<>$#cqss|ApI>mf zsx75VDo@srUoEDKU7@b4lE^q_a64g`N3^fMZlW{Ir}ffd>P`E$hZWe_iWD|=to1GR z%?l}F%U6>Bd407z#$bc!B$ezpA9XzKvwqpbO@cE@FNMKr%FuxW6JP?bGf2Jz$h~MG z;{fA~2d*Ksy}9Xtipn>*t|>587>`4IddQj08m{W2NdMt;nDc%9`Gep z#CW0vq=-MH`$A8KeC?PMm`T`s-y0kEf!fg{_3%^&;)17V*(I+--CjgT8}C+wc3asT z@j0(cAA6xoe&pVFn8k6Pjp7nf62N3%WN}pw;h}IYsQL6GG}2+rg4CAC>l{W;qRW>C z0CaOUS2P@qy}h?lLr#1Z;T~Q|z)c7*09b@Kas!2FfvZ0CZ(ySh4VnQL9UL5@j@x;A z!ir`EM*>P)MV8;MEm zdgr)8o*@s+%!Emurk>s`4kk3Elvt)FCtF`%@yW`vF)(0iNM?f_S)3Oa_p=9EQr+Az zWpv;YbJJ6WQ%Q@t0JJZu`%3@f#@HK$KabuyarF!3}QpOGt!m(iCAXS>W>$xCg` zTf}x=!PTqkdgZzT{~1>Lyp;F>)v1KQg*2Un(d!peLQ~?@V^dv%#RR=yq*P#`veBHh1EF@%3}kAc`PZwCSx%FAU7r|g{n#~X*qv~`x*u_YiMZ^W98u~p#pdx zIK{r5$#32Wb8wh|2Y@4hEPU~k`%dOIZuKUtA-HY6&CkdDDOD^~{Snc<%;u^5%DM@m{-oiQc~$Sx%X zh0X2TMi?yu;sj>AmzZXQ(}G4wG17iyiEK2*beLFyZEx`Q#sOks@v^a90(_X1l;kW$ z46PtY5V$=VG7P!6(u z)-_N|jA+1kI2b2pIn7wzyg32IxwLdKl2|ywUPZ4H7O=_@PH>Nb3%{b2jcFGWTTCM- z46(8Hhlhsr_?|+ZkK;Am%pPenx{q4{*?M{c!zUIt^!lvMqaWcZA1;+1QMQ#-yRmwr1z>`#jz5vpTu2hmI}Ux=vA+a?o;)={M1i zUe$4oP}k9;W>qxuOyo##b>J^pNpWcP?%ZN)oM+(VJdpMFl*Qh!$F#qR(CMn8WlfR4 zuYTk*hi~#Hf z`I8_{VOfyTmcz44~D=TN`f`kNiYDOj|)H_c- z!>(ffA>l<0I}=QEtLQq~3^VuzJZwyZ!TM6;LhpPOqzSB7%pBJ((PbvZVSxY3F8{xLr(LSek&zX~Myd*tZUjkK-xRx3B(B~ci=-hS$L z@7dz*ZEDSp=)3=&xI;F%bg`wzPC5G4b`=%zkoLH}e%&DhC;JrYy_x% z@P)#=_}AN8(PR7Y)B)O&EWH;9lf<0%>>4Re?U2L24({5DiAtA&0weJK`E$W$V-sM1 zSogwBG;MqqhZt&4|9#@GL5l%}5aHnOseL;uDl08;!2bJZcRTM1D$?TO6}&k#d|7aA zk)5StDc*?ft41^=_y}5(A#Xqn4Pcii2H_I*5g#;LJeQ{PEE@hkppqi2QR&XzQ>_9G zkc-P4OczwBy|!}g?dY>up{0zo6;wR4=`(**$=z7}u*HK<_9G7uTIz|P(*sRc5lJy~ z@p6_P2F1d)q&Jn`LhrppC;cJ zko=PDGJ9tB?3w5Bio7ZfNY;`n6Tt6-=uFMt%w=$le*5(6{aBEo_=B}^)CvG30c@L6 zkPrgsWiS5u4&=%-XWzXqJ#iHPBI0TV6X5!@I-kYwEB%THr?5<)w+6dw+@RQa!$ZqUkU| zMxuy*Iyie@UEST;omUD?j$~qCy@yVNHxM_Zd!|Q>L=LPcfj})0`->IXy^&&^UT@u| zk;x-=JWaMsO6QcU@J~FgSAA zy&$XbT3FfU-cM*5)n=APHDtfz-48 zrB5NEK!u#)th;8goauaEg+GJ5+mMj@MLY<@DYylmftkpQ?#~6wi?(1eaxyYNGWvRZ zi}Ld`s+8k~K=40Op?73N!`K4|q&vQY|14dBwHzn~|vjZ~u^$hW-3tONQ#=@^0Md zA8~KI!BJa&_;IQ|j^nw(8{gV@*F&MGayxO^8M6?? zwZOM$?V0Y8z>_D+lL=>)$^2b?eIcWgGX6T3Buo$pHFtWO(;cuJDQTuMT>Si$oK;NH z5)us)6E#^yAe9LiL#2MjbLY-kdy;h3^QwmIoOoTz%#XpQeZI@XF0&QQqqI=X;yzZt z*Q6Z0QSH7LHT=l-k<#l-K5z~yi5=nU*87NhK}Mhj+f!AOy?WQVyrmp+9e>xjE9P&m zXppD@Qj5?lS>VqP{$OA-V+3j0WKiV!$35}~B2esnH`KUDjZCNi3tUnTCO4P!+_Cq;*O@|V-A_UIG^SCm3VCv!{6W>nk5?`huBptcshRq0 zO46{IxiQtyd;alO;jUgm!L}Xy4aM#U6?Q>#6KSJ=1nErQ(e*x4B7N^ZdzOc=y1#@- zXkEJzkYk2?Oh0q2)SIPseIAzk8S)of39X3HW26T;Fqauu| zR6ZBaKTCYtY@l`|Gl78RMGIM8>+XfhgV?QZ{~zukNl;%kF)mYQUR-3{Wnvr^?yaMR zQVdZ^F?~3TG0Y>?0g12o=*2JB;lyk5aEjJ_PZp}%Dc(^;D3)8i4t8>$T7DXNIDhF0 z?cFIqcXfN6sqAKEIect{XWo~D?d68_bH=0@aS!>z5u{-q6 zn?tA_Da*?P=JvOEUK$@R!M`02y5Siaqnw5u_A)Noie?4}4w1d)Dk_oob&`gla&(L5 z`;15}sG#~{709)4qsvGV5}FHwoTDXDk#(8DXzq|x-7VkF@pZ{1Q+OCw>S_za5q z5J&XfSY>2n z@Nlkt$3h{$5ax1Br@K>_w*g&)X!YI~0&x$*0yx!e2o8MPJ|rb6u?Lsz4kHaw32k7< z_u`SVEbH4ePh6&ACvh9CjuPq;;i|d^j<_Z+tCs4*>DJ@g_0j^fUI&fIZdNHME%g(J zspE5m^RE9Xjc@_Uc12g(g9Q2aCAcUo$peqed?VL)1X+mri9r|!6-jF+n$fAed^<4S zMI@l?hGMyVIfyegG0}04s-DY!QNjG>K!}M(TC`aIXE07b%@otiEu3y?A6oCiL0%S z>8jZ-gCOjp*hz(bh1Z)-c}J>p%f=KZ1m6h3;Tj=6FvM|o)rVE;ls@Tbl?^GoR^d|?+$sax^vpyBE7DvzwfAYsdZ_SamRzU3=k^ z!Bfk#XOQ2?M`!ss+`A9i@8u?%tuHyxGYBfLM%%eu7Q^CMG3>ODY>AX^7d=EjoO1l? zuWOlMijK77)z^91AVP4Di@z7;xK^c*c+oZKUWaW()h-u#wG-eSDB`Daw$JEB{@5mQ z195pp_7e>#;))&7b`h4Xu-a>$%5cB#V_ z1)`9YlymW<{<9fb1N-wDZ(g(tL*epWK6fv{j@UpoD?rHM$?I>(GQH5XL5*k~Jf!Pk zv@kk~x+qKH*AeAnzAhSYcAweS*2x~Nw~cBg^tjIYdT)E zG(6k95oK+-&y#htWCGZjo}(UJ7-~Ah=7XCk8|%HcQ*SZy_u8bFE2)k3EE5b<>ev^+ zo#hQ9l@L5ljk)7Il_!M9eQmFOs68L854mp7&bF!%;alMwyX z0-FV-YnfK2(L|$OJiHnf9;U0|whR{EIn5a;J9p=k(~(MiSr+yvV3w|oP>738@V=Wg z^#M3kP}8ueC?nH(Sek5V|BE&&nck>&9&~}RWOSx^FPAKL?-!Vot?pMhVnu6xsomr6 zxBw5PKdN+YmTS3*&>e4RT+%ervb#rS3mk<#?sZKgKY^jpWceTX4tFsT;eY}&? z4VN|qs~8^5bbBL<21Bohc{gJ??M09By!>lH8U9pQy>2xG;yUtY`~Ggz+KOu4@yx6z zg;XB@3%Jn@0h8v8^iwS5byx0@CDf#cPK~>64W8_QZhK36b4z=M;8#qy>5!rPCui)w zr0!QHdO=pSPwz>A-pFNet3{v0$;#e5?{8iFFl>gjP~V4bU(r3g>4W0gG8^)E<&dv9 zu*h#YF0;Kr3z+-AtX1NeF}PWqmT&R_ok`Fm~PDnu#1`HPhHc59ZT63RC zdCI-y(VsufQJ(o)I8(T7yWGl*a9KHAoS@r$4*4F}8crD&dTU2*JcOHhQ)o7{V$f5E zI<_$aeZ2%zqM!VI*A-AaUvWk0fj$np)ZQf``fBmcGcV|Us!{DU+4k*|r`Xi`y2=vW zk3yk=G0G9~aD7wL#ihB~#mN$-@UEqklHDsG7B5~5c-Y!WNzR{##TDQYblEXm7NeN2 z^LW+l05RvK4a_9xgZf1y?f&r4VFAHnleIxno+{1wlYx?chs%mjTpaDO!wXv&!TDnvlyiCfjYA-Ofde)67Bl%{9c1N9pQtFLE=Z6*t`N zHy0hxI~Ds`GSTxA>arKojTDpyoO7v*p*KQ@%SX!h z%g^szHlys9Gc9K86gwh=KAiPp2{dG=qaz8Jq@pWj z=fE2nf9wAP>Yo~mj#U%N`?~b?>%`leVISySp*i#8oRi~lSbT7_9OJ5VvvdnH^TPsU z$j?onBo7?}^z#GKxKly}zsv2#Va( zhKy-cQbkB1Ehhzb!r2ZsO$<3Qvv9#~{UMvrR^MN7u>0J2xOSf&th$-YuH`Oi3_yLE zGA>d$$qauida#rXE{9l<>2cL?_WhGI@I0TkQ!0e7>k@vqubG^H6t@n=0r0GXzq!cB04BW7r!CYY3!Ms-MHc z7Up-FR)&q^BiE*^Jy1iJ63FLM7duSE#E)MN(y4<4xKS#^-kXv6=NL2fYDm}-V=eED z?TK?N6*oLeT&P9N?8puK=J@Ax-#WA4M31PhiB8G4rn(lH>#+H$ zwPd-r)NYZ=6iEbd%5#U{Zx*Z9)8j|p}hX%QwjQeo0k-p%kJpXLu-Qa zf*$NIxLv+TM-2^5)$)%Eo|o3i3>hHQyp`;_QzDr?$xLZoa8;bM?aUDU+#zJ8mG`9& z8=(4Qh*dcYl zVwk3HYS9yP`knf}nJP4x9B#s)1=+oG)ZI^ff*%L})2XM)4AXTM$sWy)f!17OMr9W( zBbd42tuH9}-i&%VRcmKsrKGI;wwlgRSk9Ujo!GMmc8*CdIKnTqQ2f43{rA4{uP=To z+ExiIwmtu!b>u`urGnQ)#|mLc%G#}%8?*U|?MccbQ5Dgn;@r~Z*vI9+cJj~f1?X`S z-@P=$)J#{>VGBF$l`hd8S#_Uk`SFvh9j|#K0)y4ugwBdte>9UnI)t`~s2inm_EH#e z{YByzclsqHeT47#NY(d*u*-%o_r8kqcoI=~R;BTy>0w?(A&F!erC+^$jtab4c-P_E zBdRoB#i|?%noZiXM=ogcf{+MRl{Dn@2u*4j;8+tjMxVxUNd%yMYA+$f+?jonR0srpkqG}ALU6o4l#AKTh zcPR#1M}Vlx)R+~jxSbibpDp2SyD919sm`g(cb{bX9*3c0@kTl20J^c7c`x~!S~wPe zIfNk`w=^@7Wl@>HG44_4tYO9@+sy%iK$5#_wx^p=x831##*JI&Pf1qqP!FIIGNTVD z9$=&IQbZZ6=qlw>o+V5FXr=dyr1$KPtBgmZa1!fx?qd@*2x^q+iSHoLmM*%ytUaIv%jhw1OjobLJdMZwXPnbBDiy^#Mo2*Yqp)oQl< zL`+&=FsSwX)>1y9WCTy)bSLBtqo~wi%6-qrryHF~sYh1UT_o zb__a0Y5dXNT;K}eb!|JvyIT&ZkYBvAGK}a_IL81-6^*3(qxu;RxZK<=cjrC#BG{wt zvkw!qzbHdKt&W_m)lRsrh7MX85AFqTa@Llh@E6?Fmp>uVxY$RTZYE`!LKOwk1z7rCm`>4Gw<<#{FtFOoMaydJaH|0?G<_(g(Nx-a(BsGTk zQ5v95**Du;vbVn^egA_MahsA%9t=I-{hY>yKdIeC%hlD$)`pz9>_JMMhW_6*$$n^f zn`)xsvC!^Eo{f=bvvecX6pU6cYaozMxJkF&y}++>+N6YDl5)Sw!YoedAk~(v8-)gy z=A7X+vv7b9@q3E;#wconl9DD>-O+s>bWT;%6srE)HeB3UvHn7?cR86fAvSr&X_})| zyG!2NDXguT18XB{r0DH~!hSzdjq0W3cVjVT!RANnh|AEn69!5D(^@Ky^*F3Gh&L7% z_JFDKd%ij_X(qe(X?-}6XdX;}kmrWQV2$l7JH15(#BELT z%!izlfV1e!iHchQLD z$X6P~1a@PiYJsRMh#c0BaEj&@!x`wJ=*^m6rw$QDIa`IuJ7TZxScK+e2AQ>Ahy!X; z-RjpWPlff34Q{NB?-jv3x4rFN+nerr0?}r9ubPe+Er1pmg+;Xz2ff}hX%F)+x$?;& zfOZhLv*2Q!3>SvYZ@^XGs6+@W5gtI33%JCoib@w+%P9tL?j!xN z2z~v;zRC9Z=r}ud$Q^C1)2+ra@5&D*$k1;(`a0{tFx;%=ugthBcBZO^xpj`0+OjAB z5yE0$z4hE2b8?*X1g2YhKO)A_y<0PnW~@d6QWHZ+v#Q)3Kb%mn^-A-;<=Dw|>UdUb zdY6B(d3{a)_(F5ev+=Lo>Or;PxV+M=JY~!GTE7f3QG|l6Ze6;i`mjJ@m;uLl z=3v*AcY=bP^>%dPY@DN|<@`x7n1Bgzw00g4O9>7}M|&@|$;^Jbld-!7#Gjdx)BE}9 zA?=rfBFaFD$1Y|Q4L--A#Nx@UYJY~U>{Oz()BA$qH8i|uvg6~O<1s4|Aj~)0YZVgk zSd$M(RTdnuP*D^aCBx+R4%;_5I~8U{$TxTK@h7znKvRSSfDM+*Mwh)Ju2k;mSZp@B zKH|Tc>*P>MWVOkcp9S>&FL6nD8-@HH4wyOvV^aFo#OtWJR6xyIuI_-`ZEj&!23d&f zXffUCR(n((8#^mqbEqx6sfI_pBPnYn-@Kr(Kiwl3KSY_{e}GgJm;6&3;Rpofhd$t_ zK76?26H(otD_G)?4zpXjN!wLmn`7wfcWE|&MiFc2#zIlE+HPmWsYGr%n5vn>{@zpS zMcn>wp8(y_Y#T01GJ54EDof{y!AvhN=a)W{I9J2m!k3TcXWokoq4Gq(dV8s=C=9kT z*)AyTe8k^O&crrT+R1A5U1@p8 z$Vp(hg%ZPThq?MphP!b>PT7yd^01{b zqF0kSS#JdL(LB=PG;Muf-CsWX z&1c%c){$?qfU(31A$yK2C+p2;IZgMgPd+wsk1DnoU}egSJ3=gM!ku%Wwc>d zWX?7>*BTS?FTa1XbnhH|yh=lOqYr;hQbddWe0HWdfmRT)64lfjocju%!isE)t0uv8 zDdlv=3l9izX=DNkbf^-xeORKEr&d}Pg5aFKjHOQN#=fg_KH1wxwe1}A5*t?Rl#A0O zua6G19|U-O;>gv>(MZ>+c}O3BG<$;>9{zdbuR8vW-Gl*c=N;Zhg-Vc+pp94&`#opF zTtZjYrg^^x^OjBFWxS0A%^iQ!oq;L#iHR8tEXS{*ym3MdRNAHKmS-I-S7N*SDtB-JGn=`)F$oGcQ?X00C!7<6g4&HI^71ze7v zOE{IkWu%zxdgx*(Dq~~(r{OB=6)ev8^5p^4u~Ykitd(ocs`G6P z!HSB0XJIj>!+^&exfcYjUt24#yc-+KN`IDo$ix&!85mq}IAXt=bVphDJ0(y$kQl9T zjf2u^8_{XVDqY?TD?78I{mGZYg5_JZ>PXCg$UaI}+L$(c-)|NAWA+N2w~*^p^uFy{ zY)r-O`rs_4lK#S#67=RRd|@sN0I{qRbq{$v+R~Ou0@*nz9Lq*q>ad%ADxVNXH)5s( zWTh_Sw?3Bt_GF2&z+vqM7UAA#i#K+!P>tnf>%PLDt!LAVmts z`5;Y4CPrv}b05ub_N5{U|59_bPY|W=enMwxPKr$JHsPht(a~j8Ns^H;QLfdkJ(hvU zHLg}W9NnLv+H|W7K?~YlR{Qj)mz0)3{8iMPo~g7Crd0+qbA?V}pcCkcfiW`M_99N? zr;i$&-Na41tI9T_;%ZHX2W5NJ)wv1}TN!k*lV0Q_rjr-1u+_Z0l2oE*`ry4h7QNFrT#J5UQk( zdLK8#5lB@1Tcgy}3zDkJ$|2!uyH-1kJh`^7zNAVjlj*hvE#^bgyV!WO!yNm3CDzVPH>)R5zk8>%<(I}BGhARctm3^;)nO$16su`+W zUgQ_mTo}!*w&@y4;|lIILFSB}r^5dkqRDOZDkn=aOXiP%pjp9|ndM#a?8J^lJwiU# zP5$d*z=rv9wEcXLcfI+rLb_+?z&FJlbnYTLvn>Cq?Ds5-08`xD^&tVC^^LdL+kJEO z_zCxuWob3AWWLXvXsfe!Gp{ol>?%X|o+TsX_pYx`h(l0y`3C({CkT$H8RCsAW$kJ=|VbyuHzIKfX2bkISmb-50!e?i|j1d4JoUj7msRAi{2XY{FlRwHOZ7 zO$Ca=vT)w+fr>kCj{gMe#xM0k4Keyp$q zw(M;nrChx73UI5@v84X%>DOhP&-6Q>D2OL3M zl_{-r%akI)RoHg#4eU$xKtxUUHq2E^J3S{JZOUx6HZwiUqx*Z^UbmSHT#SdK2V??6 z<1{p2JuiC~jJ2z8Iizi|=CT`b2dSoCzJey1HYhdcdBkOpm`@7_%FxUm>SZaCZLv0| zF_Z>+#QAXO+i)s2e`@0|z+l`g=78PvId%#07(Wr zpWOS36kOliWB8Q@Z*Kr5R8g01wy;MWw@SB*32EZbd1;n_|AM&fmF41ZhO9Uth-CDw zgu8|XM_N}S_}vK|YIx%Y%ki`D>lEup>v`9C&eU)#1k^w6ZP9P(8nXn}Z*lS-1a6Vj zw<$%&>-rsZ>=cjH#VzCPZM$=mM0o-m*~755gfU>ZX4Vx z1C^*B9-obqqRT_Zo~4&EHx#k8!YofEb>Ji56+dR$e}&7JSCm{<46 z3e!trpR231a@6QoJ0PhrxK0}LTPUXZQh1T)KA3!|D50s05xvDg55GcFhCg}CU-fGL z9bt_o7EQy(3S#yh)gf0M_RY0`61VC4*0TS8|5SwnGHZ&gSe)2@y|AYUa(E*%nK}LM z@VxG;19sTj&bp%mBY30I3ZG>J4ww@4u;j?CU9niLakJQTgz`MBYI4vu?>aAqsK4Z> zD9`Fr91U@)h3d^tq~E%(ZB`-$qlHfKYU{q|%=dQ!^72bPX!*g68l9Y@Yzqru=79d7 z9jZ|upDQ|bBfH>f2g^eurjNIyaTt`TjD(ZZSfEGel}|N>b4yeCal3LDkjlUu&at|W z93P&Q>Wd@(Y$%CXM`eR(AWcDMZOH;=~lB_pX?anzuVLs6`a7 z^sEh+(`7(6u`KzvOSg5|P!3oMbt+RZ^p)BZEp4y#LR&lM>ErkMgOlgK$R1v$B+COT zFX^2txhd7vrI~eg+n28$>$^pS`l=s`BcOZP7Upmc4M9R=6UD5YPcwJ}oMIJM!gp!x zPr0qCb=UOzK9h%Oti4k^RgH)bj}I=g+)P6HQTA7r42}~-#c;Ye!$>s8^vGGh>|3U z&1e?pR|peXx}`_?uA?I=V7IZjcxJ7}#ym^LmASN+aa14lIXmV3EiSaXRJ?#cT_4|W z1I|1;4$KmEq!Kqy7C`2Yh40yaEO6-Ulp!C=M2F|;)-nCI|7GK6gD`rk2fCO}*5KQ` zfD7-B+Cbu57PA{zNna#&W6jrSV#dd{g{w$V2qE#`os(qu;WVhN4trog1!?NaeMVR>>SlXrIUgk5}| z6qJ5uZee|H!Scuv#Aq-(V#e?kKW}hABKmpdekKTSoK3srLS>T&6B29Q{!I^yh%a?- zfu~s#?UoZ#mF6BaDefC4m~ZAuMutPTRR*7 zj~2lXf})W7JXw?VrdGbsRV7EN;PDUHIrxxLWd(c5UMGacNs0PzElvkb(A7J!FUA4c z#n1M}sW+BaS#;>L)2S81KH`_D*ieD{);@^e-KlrKcsGq2x zL|tszZnilXNjR3{&FFJe#mr1i@YM^x|Mb$^8x&mT{dkQ??>e|4`t`d+|Eq&TfS>@h zH=`i{ml4pcz8F9MMh0ZVrCEZpNBrPE0dHF>0Rnaatj2?+JbN1ZQCslesrkP#uK$Nq z_&P#T?85$vw2*3yYyd0K*V3q@uLRf z?3w>90Maz}%c~&09OO$|>99WSeQjsQ^WVO52d=27sE_?|7xLrdM7R9@|C-HHGLxCG UqeIhas Date: Wed, 24 Sep 2025 12:57:08 +0300 Subject: [PATCH 67/68] Update README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6318492..eb69e82 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,26 @@ ## About Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/), this project represents a simple cookies-based role-based authentication service using JWT tokens, built with FastAPI and managed by Nginx's [Auth Sub Request](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) module to verify users access to the resources of the API. -Request Flow - ## Features - Access and refresh JWT tokens stored in secure cookies - Role-based access control -- Automatic refreshment and invalidation of tokens +- Automatic tokens refreshment and invalidation - Authentication based on Nginx's subrequest module - Admin UI to manage users and roles - CLI for creating users +## How it works + +Request Flow + + ## How to protect an endpoint Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with a specific role. For example, let's say you have an endpoint `/protected` that you want to protect it from the public access: ### Step 1. Add a location block for the target to [Nginx config](proxy/default.conf.tpl): - ```nginx location = /protected { include /etc/nginx/snippets/auth_subrequest.conf; @@ -42,9 +44,6 @@ For this, you simple need to insert the endpoint into the `locations` attribute ```json "moderator": { "locations": [ - "/docs", - "/login", - "/logout", "/protected" ] } From 3a5ae8de3788cbcb48cb07ecf7dd6f4f8676e1af Mon Sep 17 00:00:00 2001 From: prathamlahoti123 Date: Mon, 6 Oct 2025 16:10:50 +0300 Subject: [PATCH 68/68] Update README.md --- README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index eb69e82..c9a6617 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## About -Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/), this project represents a simple cookies-based role-based authentication service using JWT tokens, built with FastAPI and managed by Nginx's [Auth Sub Request](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) module to verify users access to the resources of the API. +Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/), this project represents a simple cookies-based role-based authentication service using JWT tokens, built with FastAPI and managed by Nginx's [Auth Sub Request](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) module to verify users access to the protected resources of the API. ## Features @@ -19,11 +19,13 @@ Inspired by FastAPI's [OAuth2 with Password (and hashing), Bearer with JWT token ## How it works -Request Flow +

+ Request Flow +

## How to protect an endpoint -Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with a specific role. For example, let's say you have an endpoint `/protected` that you want to protect it from the public access: +Only 2 steps required to protect an endpoint, and grant access to the endpoint only to users with specific roles. For example, let's say you have an endpoint named `/protected` that you want to protect it from the public access: ### Step 1. Add a location block for the target to [Nginx config](proxy/default.conf.tpl): @@ -34,7 +36,7 @@ Only 2 steps required to protect an endpoint, and grant access to the endpoint o } ``` -The most important part in the location block the inclusion of the `auth_subrequest.conf` snippet, which contains the configuration for Nginx's subrequest authentication module. +The most important part in the location block is the inclusion of the `auth_subrequest.conf` snippet, which contains the configuration for Nginx's subrequest authentication module. ### Step 2. Grant access to the endpoint only to users with a specific role. @@ -49,7 +51,7 @@ For this, you simple need to insert the endpoint into the `locations` attribute } ``` -As a result, after authentication and authorization process, Nginx will automatically send an internal subrequest to the API for each request to the `/protected` endpoint, verifying user's JWT token and role. If the user doesn't have access to thea protected endpoint, the API will respond with a `403 Forbidden` status code. +As a result, after authentication and authorization process, Nginx will automatically send an internal subrequest to the API for each request to the `/protected` endpoint, verifying user's JWT tokens and role. If the user doesn't have access to the protected endpoint, the API will respond with the `403 Forbidden` status code. ## System Requirements @@ -63,8 +65,8 @@ As a result, after authentication and authorization process, Nginx will automati There are a few configuration files that can be modified to customize the behavior of the application: * [proxy/default.conf.tpl](proxy/default.conf.tpl). This is the configuration file for Nginx. In this file you declare the endpoints (locations) that you want to protect in your API using Nginx's subrequest module. -* `src/policy.json`. Access control policy file. This is a simple JSON file that defines the roles and their associated permissions (i.e., which endpoints they can access). -* `.env`. File containing environment variables for configuring the application. It provides settings for: +* [src/policy.json](src/policy.json). Access control policy file. This is a simple JSON file that defines the roles and their associated permissions (i.e., which endpoints they can access). +* **.env**. File containing environment variables for configuring the application. It provides settings for: * Main FastAPI application * PostgreSQL for storing users data * Redis for storing information about authentication tokens @@ -72,18 +74,18 @@ There are a few configuration files that can be modified to customize the behavi * Nginx server * Other settings -**Note**: if the `.env` file is missing, create it by copying the `.env.example` file and modifying the values as needed. +**Note**: if the **.env** file is missing, create it by copying the **.env.example** file and modifying the values as needed. ## Deployment -Once the configuration file is set up, you can deploy the application using Docker Compose: +Once the configuration files are set up, you can deploy the application using Docker Compose: ```bash docker compose up -d ``` -The will start the following services: +This will start the following services: * app - FastAPI application; * db - PostgreSQL database to store users data; * redis - Redis database to store authentication tokens data; @@ -92,15 +94,15 @@ The will start the following services: ## Usage -Once all services are up and running, you can register the first user using the CLI. +Once all services are up and running, you can register the first user via CLI. -**Note**: use the CLI script below to register a user with a specified role, such as *admin* or *moderator*. +**Note**: use the CLI [script](src/scripts/create_user.py) below to register a user with a specified role, such as *admin* or *moderator*. ```bash docker compose exec app python scripts/create_user.py ``` -After creating the user, navigate to `http://localhost/` to access the Swagger UI documentation of the API. From there, you can use the `/login` endpoint to authenticate and obtain a pair of JWT tokens that will be stored in the browser's cookies. You can then access protected endpoints based on the user's role. +After creating the user, navigate to `http://localhost/` to access the Swagger UI documentation of the API. From there, you can use the `/login` endpoint to authenticate and obtain a pair of JWT tokens that will be stored in the browser's cookies. You can then access protected endpoints based on the user's role and the corresponding [policy](src/policy.json). ## References: