From 93f3f447424fdb3f1e875660dd29cfbce42180c5 Mon Sep 17 00:00:00 2001 From: Pratheesh Prakash Date: Sat, 7 Sep 2024 12:00:47 +0530 Subject: [PATCH] WIP - Added more features --- .env.example | 1 + .gitignore | 3 + cms.sqlite | Bin 0 -> 69632 bytes main.py | 30 +- poetry.lock | 543 +++++++++++++++----------- pyproject.toml | 46 +-- src/authentication/models/__init__.py | 1 + src/authentication/models/user.py | 46 +++ src/cms/api/__init__.py | 11 + src/cms/api/article.py | 52 +++ src/cms/api/author.py | 80 ++++ src/cms/api/category.py | 67 ++++ src/cms/api/routes.py | 1 - src/cms/api/tag.py | 39 ++ src/cms/models/__init__.py | 12 + src/cms/models/article.py | 87 +++++ src/cms/models/associations.py | 18 + src/cms/models/author.py | 65 +++ src/cms/models/category.py | 45 +++ src/cms/models/image.py | 34 ++ src/cms/models/language.py | 37 ++ src/cms/models/tag.py | 43 ++ src/cms/services/articles.py | 83 ++++ src/cms/services/authors.py | 101 +++++ src/cms/services/categories.py | 94 +++++ src/cms/services/tags.py | 99 +++++ src/core/config/api.py | 35 +- src/{ => core}/database/__init__.py | 0 src/core/database/connection.py | 27 ++ src/core/database/db.py | 82 ++++ src/core/exceptions.py | 15 + src/database/models.py | 1 - src/utils/__init__.py | 0 src/utils/create_tables.py | 18 + src/utils/fill_dummy_values.py | 83 ++++ tests/test_api.py | 15 +- tests/test_db.py | 112 ++++++ 37 files changed, 1752 insertions(+), 274 deletions(-) create mode 100644 .env.example create mode 100644 cms.sqlite create mode 100644 src/authentication/models/user.py create mode 100644 src/cms/api/article.py create mode 100644 src/cms/api/author.py create mode 100644 src/cms/api/category.py delete mode 100644 src/cms/api/routes.py create mode 100644 src/cms/api/tag.py create mode 100644 src/cms/models/article.py create mode 100644 src/cms/models/associations.py create mode 100644 src/cms/models/author.py create mode 100644 src/cms/models/category.py create mode 100644 src/cms/models/image.py create mode 100644 src/cms/models/language.py create mode 100644 src/cms/models/tag.py create mode 100644 src/cms/services/articles.py create mode 100644 src/cms/services/authors.py create mode 100644 src/cms/services/categories.py create mode 100644 src/cms/services/tags.py rename src/{ => core}/database/__init__.py (100%) create mode 100644 src/core/database/connection.py create mode 100644 src/core/database/db.py create mode 100644 src/core/exceptions.py delete mode 100644 src/database/models.py create mode 100644 src/utils/__init__.py create mode 100644 src/utils/create_tables.py create mode 100644 src/utils/fill_dummy_values.py create mode 100644 tests/test_db.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..26489ec --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DB_URI="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index a9477ac..ec2940c 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.ruff_cache +.vscode \ No newline at end of file diff --git a/cms.sqlite b/cms.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..43851b63471d4533c1833bfc79280ada417fa26c GIT binary patch literal 69632 zcmeI(Pfy!s9Ki9oKmsm-bRDt~Lb7^+sAv=IY2vWC;IeEXln|u$WEo--O97|0(?5qD zfKJoi!)`n7u*=@Tj(Z_{3p?yEKaO2uVy7+I$@)g*-}BgypXc*?{y2Kt!-Kl*Tk5If zo|(S-xKJrbQsEm_EfflJ@Vpm1s9xVtdzRYLp9e`J$!*I! zaqYfuJLlt>1GhJxXu7_A(zA?=>pW}L_O#{;^?Ut=x^B0t;@VI5n|f`xA=1{bPp_&? z{i)v68{7J!8YPFP$kof$g_5$hC0!kaZH~|J&5mJuo^xWGqQV);iwT#EgM?I^mC+J$ zwT!GpBd?3uP>1K-rqMT@qLN09f~3f2Z?UMDTT;P3Z(DD?m%U&;Gt7bSgx`(0LZXNf zE@LwzqjLFZzNBcHbmb(tgA%)rJ6c3sz<5qM6@C1;k8qDp`c(S&r~M!L*M*G+V3-=k z;ILd&I@*U~5EYafhVi_G%WvmO%F>eb&I= zN16R*q_xqwn2W+xYwYOH)x;6%ej_o*U7eRo%F2rL)8o-07nL)c2zQh?A>y~0+pjg- zW^gy$s_p5AEp6{vBJd|cR>yJQX3c{=^Sm<%)}yTaz8fq{;&icSjdz@5r~P(3HRz6~ zmgx^%tDV{#z22ZRepXs18(nusnv&==ae6qaMU-~*x*n{m+uGr_wxi#imPcBgC?-zN zC`PVcOchJY{rl3zSs3tOZVvuRi9m<*d*%Y3KBOnE-#1NyT5Qael+{(~@?ltuxYqG| zF;Sm5bwba(MWt-dF;1LzN_R-9?$Ok+x<7=~jbjzRPbaDyr+#vE9|TjIK0roaGp; zubad7so)lSR@`Qn#Y;uJ@IU|o1Q0*~0R#|0009ILKmdWf3W!a8e*fn+y0i-c1Q0*~ z0R#|0009ILKmdWE0Q>*+1OyO3009ILKmY**5I_I{1oAJy{(t^sObZb}009ILKmY** z5I_I{1Q1~VpFV&90tg_000IagfB*srAb>#r1^E4+{}|Ik1Q0*~0R#|0009ILKmY** z`29~GKmY**5I_I{1Q0*~0R#|0ApZjV|Ns2Qm=+>{00IagfB*srAb{00IagfB*srAb ORJSONResponse: +def home() -> ORJSONResponse: """Home route.""" return ORJSONResponse(content={"content": "successful response."}) + + +from src.cms.api import article, author, category + +rosa.include_router(router=article) +rosa.include_router(router=author) +rosa.include_router(router=category) diff --git a/poetry.lock b/poetry.lock index 46be512..3f32e20 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,22 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# 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 = "annotated-types" @@ -23,16 +41,81 @@ files = [ ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [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 = "asyncio" +version = "3.4.3" +description = "reference implementation of PEP 3156" +optional = false +python-versions = "*" +files = [ + {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, + {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, + {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"}, + {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"}, +] + +[[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 = "certifi" version = "2024.2.2" @@ -91,51 +174,38 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "email-validator" -version = "2.1.1" +version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, ] [package.dependencies] dnspython = ">=2.0.0" idna = ">=2.0.0" -[[package]] -name = "exceptiongroup" -version = "1.2.1" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "fastapi" -version = "0.110.3" +version = "0.112.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"}, - {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}, + {file = "fastapi-0.112.1-py3-none-any.whl", hash = "sha256:bcbd45817fc2a1cd5da09af66815b84ec0d3d634eb173d1ab468ae3103e183e4"}, + {file = "fastapi-0.112.1.tar.gz", hash = "sha256:b2537146f8c23389a7faa8b03d0bd38d4986e6983874557d95eed2acc46448ef"}, ] [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" +starlette = ">=0.37.2,<0.39.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email_validator (>=2.0.0)", "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)"] +all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "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[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "graphql-core" @@ -408,7 +478,6 @@ files = [ [package.dependencies] mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.1.0" [package.extras] @@ -430,57 +499,68 @@ files = [ [[package]] name = "orjson" -version = "3.10.2" +version = "3.10.7" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:87124c1b3471a072fda422e156dd7ef086d854937d68adc266f17f32a1043c95"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b79526bd039e775ad0f558800c3cd9f3bde878a1268845f63984d37bcbb5d1"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f6dc97a6b2833a0d77598e7d016b6d964e4b0bc9576c89aa9a16fcf8ac902d"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e427ce004fe15e13dcfdbd6c9dc936abf83d85d2164ec415a8bd90954f6f781"}, - {file = "orjson-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f3e05f70ab6225ba38504a2be61935d6ebc09de2b1bc484c30cb96ca4fa24b8"}, - {file = "orjson-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4e67821e3c1f0ec5dbef9dbd0bc9cd0fe4f0d8ba5d76a07038ee3843c9ac98a"}, - {file = "orjson-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24877561fe96a3736224243d6e2e026a674a4ddeff2b02fdeac41801bd261c87"}, - {file = "orjson-3.10.2-cp310-none-win32.whl", hash = "sha256:5da4ce52892b00aa51f5c5781414dc2bcdecc8470d2d60eeaeadbc14c5d9540b"}, - {file = "orjson-3.10.2-cp310-none-win_amd64.whl", hash = "sha256:cee3df171d957e84f568c3920f1f077f7f2a69f8ce4303d4c1404b7aab2f365a"}, - {file = "orjson-3.10.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a361e7ad84452416a469cdda7a2efeee8ddc9e06e4b95938b072045e205f86dc"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b064251af6a2b7fb26e51b9abd3c1e615b53d5d5f87972263233d66d9c736a4"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:464c30c24961cc83b2dc0e5532ed41084624ee1c71d4e7ef1aaec88f7a677393"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4459005982748fda9871f04bce6a304c515afc46c96bef51e2bc81755c0f4ea0"}, - {file = "orjson-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd0cd3a113a6ea0051c4a50cca65161ee50c014a01363554a1417d9f3c4529f"}, - {file = "orjson-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a658ebc5143fbc0a9e3a10aafce4de50b01b1b0a41942038cb4bc6617f1e1d7"}, - {file = "orjson-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2fa4addaf6a6b3eb836cf92c4986d5ef9215fbdc87e4891cf8fd97990972bba0"}, - {file = "orjson-3.10.2-cp311-none-win32.whl", hash = "sha256:faff04363bfcff9cb41ab09c0ce8db84b8d4a09a374305ec5b12210dfa3154ea"}, - {file = "orjson-3.10.2-cp311-none-win_amd64.whl", hash = "sha256:7aee7b31a6acecf65a94beef2191081692891b00e8b7e02fbcc0c85002d62d0b"}, - {file = "orjson-3.10.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:38d9e9eab01131fdccbe95bff4f1d8ea197d239b5c73396e2079d07730bfa205"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfd84ecf5ebe8ec334a95950427e7ade40135032b1f00e2b17f351b0ef6dc72b"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2ba009d85c3c98006759e62150d018d622aa79012fdeefbb70a42a542582b45"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eac25b54fab6d9ccbf9dbc57555c2b52bf6d0802ea84bd2bd9670a161bd881dc"}, - {file = "orjson-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e735d90a90caf746de59becf29642c8358cafcd9b1a906ae3566efcc495324"}, - {file = "orjson-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:12feeee9089654904c2c988788eb9d521f5752c83ea410969d1f58d05ea95943"}, - {file = "orjson-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:619a7a4df76497afd2e6f1c963cc7e13658b3d58425c3a2ccf0471ad61d71025"}, - {file = "orjson-3.10.2-cp312-none-win32.whl", hash = "sha256:460d221090b451a0e78813196ec9dd28d2e33103048cfd7c1a3312a532fe3b1f"}, - {file = "orjson-3.10.2-cp312-none-win_amd64.whl", hash = "sha256:7efa93a9540e6ac9fe01167389fd7b1f0250cbfe3a8f06fe23e045d2a2d5d6ac"}, - {file = "orjson-3.10.2-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ceb283b8c048fb20bd1c703b10e710783a4f1ba7d5654358a25db99e9df94d5"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201bf2b96ba39941254ef6b02e080660861e1444ec50be55778e1c38446c2d39"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51a7b67c8cddf1a9de72d534244590103b1f17b2105d3bdcb221981bd97ab427"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde123c227e28ef9bba7092dc88abbd1933a0d7c17c58970c8ed8ec804e7add5"}, - {file = "orjson-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b51caf8720b6df448acf764312d4678aeed6852ebfa6f3aa28b6061155ffef"}, - {file = "orjson-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f124d7e813e7b3d56bb7841d3d0884fec633f5f889a27a158d004b6b37e5ca98"}, - {file = "orjson-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e33ac7a6b081688a2167b501c9813aa6ec1f2cc097c47ab5f33cca3e875da9dc"}, - {file = "orjson-3.10.2-cp38-none-win32.whl", hash = "sha256:8f4a91921270d646f50f90a9903f87baae24c6e376ef3c275fcd0ffc051117bb"}, - {file = "orjson-3.10.2-cp38-none-win_amd64.whl", hash = "sha256:148d266e300257ff6d8e8a5895cc1e12766b8db676510b4f1d79b0d07f666fdd"}, - {file = "orjson-3.10.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:27158a75e7239145cf385d2318fdb27fbcd1fc494a470ee68287147c8b214cb1"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26302b13e3f542b3e1ad1723e3543caf28e2f372391d21e1642de29c06e6209"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:712cb3aa976311ae53de116a64949392aa5e7dcceda6769d5d7169d303d5ed09"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9db3e6f23a6c9ce6c883a8e10e0eae0e2895327fb6e2286019b13153e59c672f"}, - {file = "orjson-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44787769d93d1ef9f25a80644ef020e0f30f37045d6336133e421a414c8fe51"}, - {file = "orjson-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53a43b18d280c8d18cb18437921a05ec478b908809f9e89ad60eb2fdf0ba96ac"}, - {file = "orjson-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99e270b6a13027ed4c26c2b75b06c2cfb950934c8eb0400d70f4e6919bfe24f4"}, - {file = "orjson-3.10.2-cp39-none-win32.whl", hash = "sha256:d6f71486d211db9a01094cdd619ab594156a43ca04fa24e23ee04dac1509cdca"}, - {file = "orjson-3.10.2-cp39-none-win_amd64.whl", hash = "sha256:161f3b4e6364132562af80967ac3211e6681d320a01954da4915af579caab0b2"}, - {file = "orjson-3.10.2.tar.gz", hash = "sha256:47affe9f704c23e49a0fbb9d441af41f602474721e8639e8814640198f9ae32f"}, + {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, + {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, + {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, + {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, + {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, + {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, + {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, + {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, + {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, + {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, + {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, + {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, + {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, + {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, + {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, + {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, + {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, + {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, + {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, + {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, ] [[package]] @@ -621,20 +701,21 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.2.1" +version = "2.4.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, - {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, + {file = "pydantic_settings-2.4.0-py3-none-any.whl", hash = "sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315"}, + {file = "pydantic_settings-2.4.0.tar.gz", hash = "sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88"}, ] [package.dependencies] -pydantic = ">=2.3.0" +pydantic = ">=2.7.0" python-dotenv = ">=0.21.0" [package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] @@ -657,26 +738,42 @@ toml = ["tomli (>=1.2.3)"] [[package]] name = "pytest" -version = "8.2.0" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -721,62 +818,64 @@ dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatc [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -792,32 +891,32 @@ files = [ [package.dependencies] mypy = ">=0.981" -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} [[package]] name = "ruff" -version = "0.4.2" +version = "0.6.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"}, - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"}, - {file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"}, - {file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"}, - {file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"}, - {file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"}, + {file = "ruff-0.6.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"}, + {file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"}, + {file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"}, + {file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"}, + {file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"}, + {file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"}, + {file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"}, ] [[package]] @@ -855,64 +954,64 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.29" +version = "2.0.32" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, + {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, + {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", optional = true, 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\") or extra == \"asyncio\""} typing-extensions = ">=4.6.0" [package.extras] @@ -959,20 +1058,18 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "strawberry-graphql" -version = "0.227.2" +version = "0.237.3" description = "A library for creating GraphQL APIs" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "strawberry_graphql-0.227.2-py3-none-any.whl", hash = "sha256:f03ecdd75ff8855be5dbd87f552a7f9a2f7891b0c1054b6f7c22a934523964c8"}, - {file = "strawberry_graphql-0.227.2.tar.gz", hash = "sha256:c65ddc1d3c6b9dac9987360261920f2938fdab84260074d5f677a7272a75c9b5"}, + {file = "strawberry_graphql-0.237.3-py3-none-any.whl", hash = "sha256:2dc43a5036995edf614d21c9faca8b6848a09353d167ae05f42a9d0e976e1611"}, + {file = "strawberry_graphql-0.237.3.tar.gz", hash = "sha256:8e04bb8821b303cb2cbd2c981f211b2d61bd21e3d171a638d93ceb99081194ac"}, ] [package.dependencies] -fastapi = {version = ">=0.65.2", optional = true, markers = "extra == \"fastapi\""} -graphql-core = ">=3.2.0,<3.3.0" +graphql-core = ">=3.2.0,<3.4.0" python-dateutil = ">=2.7.0,<3.0.0" -python-multipart = {version = ">=0.0.7", optional = true, markers = "extra == \"asgi\" or extra == \"debug-server\" or extra == \"fastapi\""} typing-extensions = ">=4.5.0" [package.extras] @@ -994,17 +1091,6 @@ quart = ["quart (>=0.19.3)"] sanic = ["sanic (>=20.12.2)"] starlite = ["starlite (>=1.48.0)"] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "typing-extensions" version = "4.11.0" @@ -1018,13 +1104,13 @@ files = [ [[package]] name = "uvicorn" -version = "0.29.0" +version = "0.30.6" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, - {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, + {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, + {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, ] [package.dependencies] @@ -1034,7 +1120,6 @@ h11 = ">=0.8" httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} @@ -1256,5 +1341,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "268e90f48c132ef08d0b298704a57cb8962b11a7c7313517810b45fb0f53cbb6" +python-versions = "^3.12" +content-hash = "fe80a0c7215e74549df997d96e838520590ae7b923a9299553a5a81fa380afa2" diff --git a/pyproject.toml b/pyproject.toml index 1c3e745..934b247 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,25 +5,30 @@ description = "Rosa is a simple and fast content management system focussed on m authors = ["Pratheesh Prakash "] license = "GPL-3.0-or-later" readme = "README.md" +package-mode = false [tool.poetry.dependencies] -python = "^3.10" -fastapi = "^0.110.3" -uvicorn = { extras = ["standard"], version = "^0.29.0" } -orjson = "^3.10.2" -pydantic-settings = "^2.2.1" -email-validator = "^2.1.1" +python = "^3.12" +fastapi = "^0.112.1" +uvicorn = { extras = ["standard"], version = "0.30.6" } +orjson = "^3.10.7" +pydantic-settings = "^2.4.0" +email-validator = "^2.2.0" httpx = "^0.27.0" python-multipart = "^0.0.9" itsdangerous = "^2.2.0" -pyyaml = "^6.0.1" -strawberry-graphql = { extras = ["fastapi"], version = "^0.227.2" } -pytest = "^8.2.0" -sqlalchemy = "^2.0.29" -ruff = "^0.4.2" +pyyaml = "^6.0.2" +strawberry-graphql = "^0.237.3" +pytest = "^8.3.2" +sqlalchemy = {extras = ["asyncio"], version = "2.0.32"} +ruff = "^0.6.1" refurb = "^2.0.0" pydocstyle = "^6.3.0" isort = "^5.13.2" +asyncio = "^3.4.3" +aiosqlite = "^0.20.0" +asyncpg = "^0.29.0" +pytest-asyncio = "^0.23.8" [build-system] @@ -42,6 +47,7 @@ warn_required_dynamic_aliases = true warn_untyped_fields = false [tool.ruff] +src = ["src"] target-version = "py312" line-length = 100 exclude = [ @@ -59,6 +65,7 @@ exclude = [ ".pytest_cache", ".pytype", ".ruff_cache", + ".env.example", ".svn", ".tox", ".venv", @@ -83,22 +90,7 @@ line-ending = "auto" [tool.ruff.lint] -select = [ - "E", - "F", - "C90", - "W", - "I", - "D", - "UP", - "ERA", - "PD", - "PL", - "TRY", - "NPY", - "FURB", - "RUF", -] +select = ["ALL"] ignore = ["UP038", "D104"] diff --git a/src/authentication/models/__init__.py b/src/authentication/models/__init__.py index e69de29..b7bb9be 100644 --- a/src/authentication/models/__init__.py +++ b/src/authentication/models/__init__.py @@ -0,0 +1 @@ +from .user import User \ No newline at end of file diff --git a/src/authentication/models/user.py b/src/authentication/models/user.py new file mode 100644 index 0000000..f887a81 --- /dev/null +++ b/src/authentication/models/user.py @@ -0,0 +1,46 @@ +"""User model.""" + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + + +class User(Base): + """User schema.""" + + __tablename__: str = "user" + + id: Mapped[int] = mapped_column( + name="id", + primary_key=True, + autoincrement="auto", + type_=INTEGER, + ) + name: Mapped[str] = mapped_column( + name="username", + type_=TEXT, + nullable=False, + unique=True, + ) + + email: Mapped[str] = mapped_column( + name="email", + type_=TEXT, + nullable=False, + unique=True, + ) + + is_superuser: Mapped[bool] = mapped_column(default=False) + + password_hash: Mapped[str] = mapped_column( + name="password_hash", + type_=TEXT, + nullable=False, + ) + + published_article_id: Mapped[int] = mapped_column( + ForeignKey("article.id"), + type_=INTEGER, + nullable=True, + ) diff --git a/src/cms/api/__init__.py b/src/cms/api/__init__.py index e69de29..ffd4345 100644 --- a/src/cms/api/__init__.py +++ b/src/cms/api/__init__.py @@ -0,0 +1,11 @@ +"""API routes.""" + +from .article import article +from .author import author +from .category import category + +__all__: list[str] = [ + "article", + "author", + "category", +] diff --git a/src/cms/api/article.py b/src/cms/api/article.py new file mode 100644 index 0000000..7b48fed --- /dev/null +++ b/src/cms/api/article.py @@ -0,0 +1,52 @@ +"""Article routes.""" + +from fastapi import APIRouter, Query, status +from fastapi.responses import ORJSONResponse +from src.cms.services.articles import fetch_latest_articles_from_db +from src.core.config.api import ArticleConfig + +article = APIRouter( + prefix="/article", + tags=["article"], + default_response_class=ORJSONResponse, +) + + +@article.get( + path="/", + response_class=ORJSONResponse, + tags=["article"], + summary="List all articles in reverse chronological order.", + description="Get the list of all articles ordered in reverse chronological order.", + deprecated=False, + name="Article", + status_code=status.HTTP_200_OK, +) +def fetch_articles( + fetch_count: int = Query( + default=ArticleConfig.DEFAULT_FETCHABLE_ARTICLES, + alias="fetch_count", + le=ArticleConfig.MAX_FETCHABLE_ARTICLES, + ge=ArticleConfig.MIN_FETCHABLE_ARTICLES, + ), +) -> ORJSONResponse: + """ + Fetch articles and return. + + Parameters + ---------- + fetch_count : int, optional + Number of articles to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of articles in reverse chronological order. + """ + if fetch_count > ArticleConfig.MAX_FETCHABLE_ARTICLES: + fetch_count = ArticleConfig.MAX_FETCHABLE_ARTICLES + elif fetch_count < ArticleConfig.MIN_FETCHABLE_ARTICLES: + fetch_count = ArticleConfig.MIN_FETCHABLE_ARTICLES + + articles = fetch_latest_articles_from_db(n=fetch_count) + return ORJSONResponse(content=articles) diff --git a/src/cms/api/author.py b/src/cms/api/author.py new file mode 100644 index 0000000..eca1a00 --- /dev/null +++ b/src/cms/api/author.py @@ -0,0 +1,80 @@ +"""Author routes.""" + +from fastapi import APIRouter, Query, status +from fastapi.responses import ORJSONResponse +from src.cms.services.authors import fetch_articles_by_author, fetch_authors_from_db +from src.core.config.api import ArticleConfig + +author = APIRouter( + prefix="/author", + tags=["author"], + default_response_class=ORJSONResponse, +) + + +@author.get( + path="/", + response_class=ORJSONResponse, + tags=["author"], + summary="List all published authors.", + description="List all published authors in the alphabetic order of their first name", + deprecated=False, + name="Author", + status_code=status.HTTP_200_OK, +) +def fetch_authors( + fetch_count: int = Query( + default=ArticleConfig.DEFAULT_FETCHABLE_ARTICLES, + alias="fetch_count", + le=ArticleConfig.MAX_FETCHABLE_ARTICLES, + ge=ArticleConfig.MIN_FETCHABLE_ARTICLES, + ), +) -> ORJSONResponse: + """ + Fetch authors and return. + + Parameters + ---------- + fetch_count : int, optional + Number of authors to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of authors in alphabetical order. + """ + if fetch_count > ArticleConfig.MAX_FETCHABLE_ARTICLES: + fetch_count = ArticleConfig.MAX_FETCHABLE_ARTICLES + elif fetch_count < ArticleConfig.MIN_FETCHABLE_ARTICLES: + fetch_count = ArticleConfig.MIN_FETCHABLE_ARTICLES + + authors = fetch_authors_from_db() + return ORJSONResponse(content=authors) + + +@author.get( + path="/{author_slug}/", + response_class=ORJSONResponse, + tags=["author", "article"], + summary="List all articles in reverse chronological order.", + description="Get the list of all articles ordered in reverse chronological order.", + deprecated=False, + name="Category-wise articles", + status_code=status.HTTP_200_OK, +) +def fetch_articles(author_slug: str) -> ORJSONResponse: + """ + Fetch articles by author and return. + + Parameters + ---------- + fetch_count : int, optional + Number of articles to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of articles in reverse chronological order. + """ + articles = fetch_articles_by_author(author_slug=author_slug) + return ORJSONResponse(content=articles) diff --git a/src/cms/api/category.py b/src/cms/api/category.py new file mode 100644 index 0000000..54bedff --- /dev/null +++ b/src/cms/api/category.py @@ -0,0 +1,67 @@ +"""Category routes.""" + +from fastapi import APIRouter, status +from fastapi.responses import ORJSONResponse +from src.cms.services.categories import fetch_articles_by_category, fetch_categories_from_db + +category = APIRouter( + prefix="/category", + tags=["category"], + default_response_class=ORJSONResponse, +) + + +@category.get( + path="/", + response_class=ORJSONResponse, + tags=["category"], + summary="Get a list of all categories ordered by their order index.", + description="Get a list of all categories ordered by their order index.", + deprecated=False, + name="Category", + status_code=status.HTTP_200_OK, +) +def fetch_categories() -> ORJSONResponse: + """ + Fetch categories and return. + + Parameters + ---------- + fetch_count : int, optional + Number of categories to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of categories in order of their index. + """ + categories = fetch_categories_from_db() + return ORJSONResponse(content=categories) + + +@category.get( + path="/{category_name}/", + response_class=ORJSONResponse, + tags=["category", "article"], + summary="List all articles in reverse chronological order.", + description="Get the list of all articles ordered in reverse chronological order.", + deprecated=False, + name="Category-wise articles", + status_code=status.HTTP_200_OK, +) +def fetch_articles(category_name: str) -> ORJSONResponse: + """ + Fetch articles by category and return. + + Parameters + ---------- + fetch_count : int, optional + Number of articles to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of articles in reverse chronological order. + """ + articles = fetch_articles_by_category(category_slug=category_name) + return ORJSONResponse(content=articles) diff --git a/src/cms/api/routes.py b/src/cms/api/routes.py deleted file mode 100644 index 5365d07..0000000 --- a/src/cms/api/routes.py +++ /dev/null @@ -1 +0,0 @@ -"""CMS routes.""" diff --git a/src/cms/api/tag.py b/src/cms/api/tag.py new file mode 100644 index 0000000..7574b3f --- /dev/null +++ b/src/cms/api/tag.py @@ -0,0 +1,39 @@ +"""Tag routes.""" + +from fastapi import APIRouter, status +from fastapi.responses import ORJSONResponse + +tag = APIRouter( + prefix="/tag", + tags=["tag"], + default_response_class=ORJSONResponse, +) + + + +@tag.get( + path="/{tag_name}/", + response_class=ORJSONResponse, + tags=["tag", "article"], + summary="List all articles in reverse chronological order.", + description="Get the list of all articles ordered in reverse chronological order.", + deprecated=False, + name="Tag-wise articles", + status_code=status.HTTP_200_OK, +) +def fetch_articles(category_name: str) -> ORJSONResponse: + """ + Fetch articles by tag and return. + + Parameters + ---------- + fetch_count : int, optional + Number of articles to fetch, by default 10. + + Returns + ------- + ORJSONResponse + Returns list of articles in reverse chronological order. + """ + articles = fetch_articles_by_category(category_slug=category_name) + return ORJSONResponse(content=articles) diff --git a/src/cms/models/__init__.py b/src/cms/models/__init__.py index e69de29..02713db 100644 --- a/src/cms/models/__init__.py +++ b/src/cms/models/__init__.py @@ -0,0 +1,12 @@ +"""CMS models definitions.""" + +from sqlalchemy import Column, ForeignKey, Table +from src.authentication.models import User +from src.core.database.db import Base + +from .article import Article +from .author import Author +from .category import Category +from .image import Image +from .language import Language +from .tag import Tag diff --git a/src/cms/models/article.py b/src/cms/models/article.py new file mode 100644 index 0000000..b6503f7 --- /dev/null +++ b/src/cms/models/article.py @@ -0,0 +1,87 @@ +"""Article model definition.""" + +from datetime import datetime +from typing import Any + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm.relationships import _RelationshipDeclared +from sqlalchemy.types import BOOLEAN, INTEGER, TEXT, TIMESTAMP +from src.core.database.db import Base + +from .associations import article_author_association, article_tag_association + + +class Article(Base): + """Article schema.""" + + __tablename__: str = "article" + + id: Mapped[int] = mapped_column( + name="id", + primary_key=True, + autoincrement="auto", + type_=INTEGER, + index=True, + ) + title: Mapped[str] = mapped_column( + name="title", + type_=TEXT, + ) + description: Mapped[str] = mapped_column( + name="description", + type_=TEXT, + ) + published_date: Mapped[datetime] = mapped_column( + name="published_date", + type_=TIMESTAMP(timezone=True), + ) + category_id: Mapped[int] = mapped_column( + ForeignKey("category.id", ondelete="CASCADE"), + type_=INTEGER, + nullable=False, + ) + language_id: Mapped[int] = mapped_column( + ForeignKey("language.id", ondelete="CASCADE"), + type_=INTEGER, + nullable=False, + ) + primary_image: Mapped[str] = mapped_column( + name="primary_image", + type_=TEXT, + ) + body: Mapped[str] = mapped_column( + name="body", + type_=TEXT, + ) + is_featured: Mapped[bool] = mapped_column( + name="is_featured", + type_=BOOLEAN, + ) + slug: Mapped[str] = mapped_column( + name="slug", + type_=TEXT, + nullable=False, + ) + + authors: _RelationshipDeclared[Any] = relationship( + "Author", + secondary=article_author_association, + back_populates="articles", + cascade="all, delete", + ) + tags: _RelationshipDeclared[Any] = relationship( + "Tag", + secondary=article_tag_association, + back_populates="articles", + ) + + optional_images: _RelationshipDeclared[Any] = relationship( + "Image", + backref="articles", + ) + + publisher: _RelationshipDeclared[Any] = relationship( + "User", + backref="user_articles", + ) diff --git a/src/cms/models/associations.py b/src/cms/models/associations.py new file mode 100644 index 0000000..92a34e4 --- /dev/null +++ b/src/cms/models/associations.py @@ -0,0 +1,18 @@ +"""Database model associations.""" + +from sqlalchemy import Column, ForeignKey, Table +from src.core.database.db import Base + +article_author_association = Table( + "article_author_association", + Base.metadata, + Column("article_id", ForeignKey("article.id"), primary_key=True), + Column("author_id", ForeignKey("author.id"), primary_key=True), +) + +article_tag_association = Table( + "article_tag_association", + Base.metadata, + Column("article_id", ForeignKey("article.id"), primary_key=True), + Column("tag_id", ForeignKey("tag.id"), primary_key=True), +) diff --git a/src/cms/models/author.py b/src/cms/models/author.py new file mode 100644 index 0000000..b5bbe13 --- /dev/null +++ b/src/cms/models/author.py @@ -0,0 +1,65 @@ +"""Author model definition.""" + +from typing import Any + +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm.relationships import _RelationshipDeclared +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + +from .associations import article_author_association + + +class Author(Base): + """Author schema.""" + + __tablename__: str = "author" + + id: Mapped[int] = mapped_column( + name="id", + type_=INTEGER, + primary_key=True, + autoincrement=True, + ) + + first_name: Mapped[str] = mapped_column( + name="first_name", + type_=TEXT, + ) + + last_name: Mapped[str] = mapped_column( + name="last_name", + type_=TEXT, + ) + biography: Mapped[str] = mapped_column( + name="biography", + type_=TEXT, + ) + photo_url: Mapped[str] = mapped_column( + name="photo_url", + type_=TEXT, + ) + email: Mapped[str] = mapped_column( + name="email", + type_=TEXT, + ) + website: Mapped[str] = mapped_column( + name="website", + type_=TEXT, + nullable=True, + ) + + slug: Mapped[str] = mapped_column( + name="slug", + type_=TEXT, + nullable=False, + unique=True, + ) + + articles: _RelationshipDeclared[Any] = relationship( + "Article", + secondary=article_author_association, + back_populates="authors", + ) + + diff --git a/src/cms/models/category.py b/src/cms/models/category.py new file mode 100644 index 0000000..05d937e --- /dev/null +++ b/src/cms/models/category.py @@ -0,0 +1,45 @@ +"""Category model definition.""" + +from typing import Any + +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm.relationships import _RelationshipDeclared +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + + +class Category(Base): + """Category schema.""" + + __tablename__: str = "category" + + id: Mapped[int] = mapped_column( + name="id", + type_=INTEGER, + primary_key=True, + ) + name: Mapped[str] = mapped_column( + name="name", + type_=TEXT, + ) + order: Mapped[int] = mapped_column( + name="order", + type_=INTEGER, + default=0, + nullable=False, + ) + description: Mapped[str] = mapped_column( + name="description", + type_=TEXT, + ) + slug: Mapped[str] = mapped_column( + name="slug", + type_=TEXT, + nullable=False, + unique=True, + ) + + articles: _RelationshipDeclared[Any] = relationship( + "Article", + backref="categories", + ) diff --git a/src/cms/models/image.py b/src/cms/models/image.py new file mode 100644 index 0000000..1a6e0b9 --- /dev/null +++ b/src/cms/models/image.py @@ -0,0 +1,34 @@ +"""Image model defintions.""" + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + + +class Image(Base): + """Image schema.""" + + __tablename__: str = "image" + + id: Mapped[int] = mapped_column( + name="id", + type_=INTEGER, + primary_key=True, + ) + title: Mapped[str] = mapped_column( + name="title", + type_=TEXT, + ) + description: Mapped[str] = mapped_column( + name="description", + type_=TEXT, + ) + url: Mapped[str] = mapped_column( + name="url", + type_=TEXT, + ) + article_id: Mapped[int] = mapped_column( + ForeignKey("article.id"), + type_=INTEGER, + ) diff --git a/src/cms/models/language.py b/src/cms/models/language.py new file mode 100644 index 0000000..bbaadf6 --- /dev/null +++ b/src/cms/models/language.py @@ -0,0 +1,37 @@ +"""Language model definition.""" + +from typing import Any + +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm.relationships import _RelationshipDeclared +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + + +class Language(Base): + """Language schema.""" + + __tablename__: str = "language" + + id: Mapped[int] = mapped_column( + name="id", + type_=INTEGER, + primary_key=True, + ) + name: Mapped[str] = mapped_column( + name="name", + type_=TEXT, + ) + description: Mapped[str] = mapped_column( + name="description", + type_=TEXT, + ) + iso_code: Mapped[str] = mapped_column( + name="iso_code", + type_=TEXT, + default="ml", + ) + articles: _RelationshipDeclared[Any] = relationship( + "Article", + backref="languages", + ) diff --git a/src/cms/models/tag.py b/src/cms/models/tag.py new file mode 100644 index 0000000..d5ca059 --- /dev/null +++ b/src/cms/models/tag.py @@ -0,0 +1,43 @@ +"""Tag model definition.""" + +from typing import Any + +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm.relationships import _RelationshipDeclared +from sqlalchemy.types import INTEGER, TEXT +from src.core.database.db import Base + +from .associations import article_tag_association + + +class Tag(Base): + """Tag schema.""" + + __tablename__: str = "tag" + + id: Mapped[int] = mapped_column( + name="id", + type_=INTEGER, + primary_key=True, + ) + name: Mapped[str] = mapped_column( + name="name", + type_=TEXT, + ) + description: Mapped[str] = mapped_column( + name="description", + type_=TEXT, + ) + + slug: Mapped[str] = mapped_column( + name="slug", + type_=TEXT, + nullable=False, + unique=True, + ) + + articles: _RelationshipDeclared[Any] = relationship( + "Article", + secondary=article_tag_association, + back_populates="tags", + ) diff --git a/src/cms/services/articles.py b/src/cms/services/articles.py new file mode 100644 index 0000000..f5998f4 --- /dev/null +++ b/src/cms/services/articles.py @@ -0,0 +1,83 @@ +"""Articles services.""" + +from collections.abc import Sequence + +from sqlalchemy import ScalarResult, Select, desc, select +from src.cms.models.article import Article +from src.cms.models.category import Category +from src.core.database.db import DatabaseSessionManager + + +async def fetch_latest_articles_from_db(n: int) -> Sequence[Article]: + """Fetch 'n' articles sorted by reverse-chronological order of 'published_date'.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Article] = await session.scalars( + select(Article) + .order_by( + desc(Article.published_date), + ) + .limit(n), + ) + return result.fetchall() + +async def fetch_article_by_id(article_id: int) -> Article | None: + """Fetch an article by its ID.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Article] = await session.scalars( + select(Article).where(Article.id == article_id), + ) + if result is None: + raise ValueError + return result.first() + + +async def fetch_article_by_slug(category_slug: str, article_slug: str) -> Article | None: + """Fetch an article by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[Article]] = select(Article).where( + Article.slug == article_slug, + Article.category_id == select(Category.id).where(Category.slug == category_slug), + ) + return (await session.execute(query_)).scalars().first() + + +async def get_category_id(category: str) -> int | None: + """Get category id.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + stmt = select(Category.id).where(Category.slug == category) + return (await session.execute(stmt)).scalar() + + +async def update_article(article: Article) -> None: + """Update an article.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(article) + await session.commit() + +async def create_article(article: Article) -> None: + """Create an article.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(article) + await session.commit() + + +async def delete_article(article: Article) -> None: + """Delete an article.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + await session.delete(article) + await session.commit() + diff --git a/src/cms/services/authors.py b/src/cms/services/authors.py new file mode 100644 index 0000000..4841d36 --- /dev/null +++ b/src/cms/services/authors.py @@ -0,0 +1,101 @@ +"""Author services.""" + +from collections.abc import Sequence + +from sqlalchemy import ScalarResult, Select, desc, select +from sqlalchemy.orm import joinedload +from src.cms.models import Article, Author +from src.core.database.db import DatabaseSessionManager + + +async def fetch_authors_from_db() -> Sequence[Author]: + """Fetch 'n' authors from databased sorted by first name.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Author] = await session.scalars( + select(Author) + .order_by( + Author.first_name, + ), + ) + return result.fetchall() + +async def fetch_author_by_id(author_id: int) -> Author | None: + """Fetch a author by its ID.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Author] = await session.scalars( + select(Author).where(Author.id == author_id), + ) + if result is None: + raise ValueError + return result.first() + + +async def fetch_author_by_slug(author_slug: str) -> Author | None: + """Fetch a category by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[Author]] = select(Author).where( + Author.slug == author_slug, + + ) + return (await session.execute(query_)).scalars().first() + +async def fetch_articles_by_author(author_slug: str) -> Sequence[Article] | None: + """Fetch an articles by an author.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_ = ( + select(Author) + .options(joinedload(Author.articles)) + .where(Author.slug == author_slug) + .order_by( + desc(Article.published_date), + ) + ) + + author: Author | None = (await session.execute(query_)).scalars().first() + if author is None: + raise ValueError + return author.articles + + +async def get_author_id(author_slug: str) -> int | None: + """Get category id.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[int]] = select(Author.id).where(Author.slug == author_slug) + return (await session.execute(query_)).scalar() + + +async def update_author(author: Author) -> None: + """Update a author.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(author) + await session.commit() + +async def create_author(author: Author) -> None: + """Create a author.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(author) + await session.commit() + + +async def delete_author(author: Author) -> None: + """Delete an article.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + await session.delete(author) + await session.commit() + diff --git a/src/cms/services/categories.py b/src/cms/services/categories.py new file mode 100644 index 0000000..2dc06dd --- /dev/null +++ b/src/cms/services/categories.py @@ -0,0 +1,94 @@ +"""Category services.""" + +from collections.abc import Sequence + +from sqlalchemy import ScalarResult, Select, desc, select +from src.cms.models import Article, Category +from src.core.database.db import DatabaseSessionManager + + +async def fetch_categories_from_db(n: int = 10) -> Sequence[Category]: + """Fetch 'n' categories sorted by order index.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Category] = await session.scalars( + select(Category) + .order_by( + desc(Category.order), + ) + .limit(n), + ) + return result.fetchall() + +async def fetch_category_by_id(category_id: int) -> Category | None: + """Fetch a category by its ID.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Category] = await session.scalars( + select(Category).where(Category.id == category_id), + ) + if result is None: + raise ValueError + return result.first() + + +async def fetch_category_by_slug(category_slug: str) -> Category | None: + """Fetch a category by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[Category]] = select(Category).where( + Category.slug == category_slug, + + ) + return (await session.execute(query_)).scalars().first() + +async def fetch_articles_by_category(category_slug: str) -> Sequence[Article] | None: + """Fetch an article by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[Article]] = select(Article).where( + Article.category_id == select(Category.id).where(Category.slug == category_slug), + ).order_by( + desc(Article.published_date), + ) + return (await session.execute(query_)).scalars().all() + + +async def get_category_id(category_slug: str) -> int | None: + """Get category id.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[int]] = select(Category.id).where(Category.slug == category_slug) + return (await session.execute(query_)).scalar() + + +async def update_category(category: Category) -> None: + """Update a category.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(category) + await session.commit() + +async def create_category(category: Category) -> None: + """Create a category.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(category) + await session.commit() + + +async def delete_category(category: Category) -> None: + """Delete an article.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + await session.delete(category) + await session.commit() + diff --git a/src/cms/services/tags.py b/src/cms/services/tags.py new file mode 100644 index 0000000..d78952c --- /dev/null +++ b/src/cms/services/tags.py @@ -0,0 +1,99 @@ +"""Tag services.""" + +from collections.abc import Sequence + +from sqlalchemy import ScalarResult, Select, desc, select +from sqlalchemy.orm import joinedload +from src.cms.models import Article, Tag +from src.core.database.db import DatabaseSessionManager + + +async def fetch_tags_from_db(n: int = 10) -> Sequence[Tag]: + """Fetch 'n' tags.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Tag] = await session.scalars( + select(Tag) + .limit(n), + ) + return result.fetchall() + +async def fetch_tag_by_id(tag_id: int) -> Tag | None: + """Fetch a tag by its ID.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + result: ScalarResult[Tag] = await session.scalars( + select(Tag).where(Tag.id == tag_id), + ) + if result is None: + raise ValueError + return result.first() + + +async def fetch_tag_by_slug(tag_slug: str) -> Tag | None: + """Fetch a tag by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_: Select[tuple[Tag]] = select(Tag).where( + Tag.slug == tag_slug, + + ) + return (await session.execute(query_)).scalars().first() + +async def fetch_articles_by_tag(tag_slug: str) -> Sequence[Article] | None: + """Fetch an article by its slug.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_ = ( + select(Tag) + .options(joinedload(Tag.articles)) + .where(Tag.slug == tag_slug) + .order_by( + desc(Article.published_date), + ) + ) + + tag_: Tag | None = (await session.execute(query_)).scalars().first() + if tag_ is None: + raise ValueError + return tag_.articles + + +async def get_tag_id(tag_slug: str) -> int | None: + """Get tag id.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + query_ = select(Tag.id).where(Tag.slug == tag_slug) + return (await session.execute(query_)).scalar() + + +async def update_tag(tag: Tag) -> None: + """Update a tag.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(tag) + await session.commit() + +async def create_tag(tag: Tag) -> None: + """Create a Tag.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + session.add(tag) + await session.commit() + + +async def delete_tag(tag: Tag) -> None: + """Delete a Tag.""" + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + await session.delete(tag) + await session.commit() + diff --git a/src/core/config/api.py b/src/core/config/api.py index 796f31d..12a8a5a 100644 --- a/src/core/config/api.py +++ b/src/core/config/api.py @@ -16,5 +16,38 @@ class APIConfig: ] TITLE: str = "rosa" - DESCRIPTION: str = "Rosa is a simple, modular, customisable and fast CMS." + SUMMARY: str = "Rosa is a REST API for a simple, modular, customisable and fast CMS." + DESCRIPTION: str = """ + Rosa + === + + --- + + Rosa is the backend for a simple, modular, customisable and fast CMS. + + It is built on the following design principles. + + - Customisable + - Quick responding + - Flexible with load + - Minimalistic + - Extendable + - Maintainable + - Fully open source + - Full test coverage + - Fully typed + - Fully documented + - Compliance to PEP8, PEP257, and PEP484 guidelines + + You can use it as the backend of your own project or to create your own custom CMS. + + """ VERSION: str = "v1" + + +class ArticleConfig: + """Article configurations.""" + + DEFAULT_FETCHABLE_ARTICLES: int = 10 + MAX_FETCHABLE_ARTICLES: int = 100 + MIN_FETCHABLE_ARTICLES: int = 1 diff --git a/src/database/__init__.py b/src/core/database/__init__.py similarity index 100% rename from src/database/__init__.py rename to src/core/database/__init__.py diff --git a/src/core/database/connection.py b/src/core/database/connection.py new file mode 100644 index 0000000..6f984d8 --- /dev/null +++ b/src/core/database/connection.py @@ -0,0 +1,27 @@ +"""Database related routines.""" + +import os + +import orjson +from dotenv import load_dotenv +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.ext.asyncio.engine import AsyncEngine + +load_dotenv() + + +def orjson_serialiser(object_: object) -> str: + """User orjson to serialise.""" + return orjson.dumps(object_).decode() + + +db_uri: str | None = os.getenv("DB_URI") + +if db_uri is None: + raise FileNotFoundError +else: + engine: AsyncEngine = create_async_engine( + url=db_uri, + json_deserializer=orjson.loads, + json_serializer=orjson_serialiser, + ) diff --git a/src/core/database/db.py b/src/core/database/db.py new file mode 100644 index 0000000..c5352f7 --- /dev/null +++ b/src/core/database/db.py @@ -0,0 +1,82 @@ +"""Database session manager.""" + +import contextlib +from collections.abc import AsyncIterator + +from sqlalchemy.ext.asyncio import ( + AsyncConnection, + AsyncEngine, + AsyncSession, + async_sessionmaker, + create_async_engine, +) +from sqlalchemy.orm import DeclarativeBase +from src.core.exceptions import DatabaseSessionInitialisationError + +from .connection import engine + + +class Base(DeclarativeBase): + """SQLAlchemy declarative base.""" + + +class DatabaseSessionManager: + """Asynchronous database session manager.""" + + def __init__(self) -> None: + self._engine: AsyncEngine | None = engine + self._sessionmaker: async_sessionmaker | None = async_sessionmaker( + autocommit=False, bind=self._engine, class_=AsyncSession, + ) + + def initialise(self, host: str | None = None) -> None: + """Initialise database session.""" + if self._engine is None: + if host is None: + raise DatabaseSessionInitialisationError + self._engine = create_async_engine(host) + self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine) + + async def close(self) -> None: + """Close database session.""" + if self._engine is None: + raise DatabaseSessionInitialisationError + await self._engine.dispose() + self._engine = None + self._sessionmaker = None + + @contextlib.asynccontextmanager + async def connect(self) -> AsyncIterator[AsyncConnection]: + """Create database connection.""" + if self._engine is None: + raise DatabaseSessionInitialisationError + + async with self._engine.begin() as connection: + try: + yield connection + except Exception: + await connection.rollback() + raise + + @contextlib.asynccontextmanager + async def session(self) -> AsyncIterator[AsyncSession]: + """Create database session.""" + if self._sessionmaker is None: + raise DatabaseSessionInitialisationError + + session = self._sessionmaker() + try: + yield session + except Exception: + await session.rollback() + raise + finally: + await session.close() + + async def create_all(self, connection: AsyncConnection) -> None: + """Create all database tables.""" + await connection.run_sync(Base.metadata.create_all) + + async def drop_all(self, connection: AsyncConnection) -> None: + """Drop all database tables.""" + await connection.run_sync(Base.metadata.drop_all) diff --git a/src/core/exceptions.py b/src/core/exceptions.py new file mode 100644 index 0000000..b393836 --- /dev/null +++ b/src/core/exceptions.py @@ -0,0 +1,15 @@ +"""Custom exception definitions.""" + + +class DatabaseSessionInitialisationError(Exception): + """Raise custom exception when database session uninitialised.""" + + def __str__(self) -> str: + """Error representation. + + Returns + ------- + str + Error message. + """ + return "Database session manager is not initialised. Please initialise." diff --git a/src/database/models.py b/src/database/models.py deleted file mode 100644 index 7bb09cc..0000000 --- a/src/database/models.py +++ /dev/null @@ -1 +0,0 @@ -"""Database models.""" diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/create_tables.py b/src/utils/create_tables.py new file mode 100644 index 0000000..f1ff1c4 --- /dev/null +++ b/src/utils/create_tables.py @@ -0,0 +1,18 @@ +"""Create tables.""" + +import asyncio + +from src.authentication.models import * +from src.cms.models import * +from src.core.database.db import DatabaseSessionManager + + +async def init_tables() -> None: + """Create all tables in the database in asyncmode.""" + db_session = DatabaseSessionManager() + async with db_session.connect() as connection: + await db_session.create_all(connection) + + +if __name__ == "__main__": + asyncio.run(init_tables()) diff --git a/src/utils/fill_dummy_values.py b/src/utils/fill_dummy_values.py new file mode 100644 index 0000000..7d39965 --- /dev/null +++ b/src/utils/fill_dummy_values.py @@ -0,0 +1,83 @@ +"""Fill dummy values in the database.""" + +import asyncio +import random +from datetime import datetime +from string import ascii_lowercase + +from sqlalchemy import select +from src.authentication.models.user import User +from src.cms.models.article import Article +from src.core.database.db import DatabaseSessionManager + + +async def add_dummy_user() -> None: + """ + Add a dummy user to the database. + + Returns + ------- + None + """ + db_session = DatabaseSessionManager() + db_session.connect() + async with db_session.session() as session: + user = User( + name="dummy_user", + email="dummy_user@example.com", + password_hash="asbaasdasdasdasdbasd1123213$!@#!@#ASDASD", + is_superuser=False, + ) + session.add(user) + await session.commit() + + +async def add_dummy_entries(n: int) -> None: + """ + Add 'n' number of random dummy entries to the article database. + + Parameters + ---------- + n : int + Number of dummy entries to add. + + Returns + ------- + None + """ + db_session = DatabaseSessionManager() + async with db_session.connect() as connection: + + users = await connection.scalars(connection.execute(select(User))).fetchall() + categories = await connection.scalars( + connection.execute(select(Article.category_id).distinct()), + ).fetchall() + languages = await connection.scalars( + connection.execute(select(Article.language_id).distinct()), + ).fetchall() + + for _ in range(n): + author_id = random.choice(users).id + category_id = random.choice(categories).category_id + language_id = random.choice(languages).language_id + + title: str = "".join(random.choice(ascii_lowercase) for _ in range(10)) + description: str = "".join(random.choice(ascii_lowercase) for _ in range(20)) + + article = Article( + title=title, + description=description, + published_date=datetime.now(), + category_id=category_id, + language_id=language_id, + primary_image="", + body="", + is_featured=False, + slug=title.lower().replace(" ", "-"), + author_id=author_id, + ) + await connection.add(article) + + +if __name__ == "__main__": + asyncio.run(add_dummy_user()) diff --git a/tests/test_api.py b/tests/test_api.py index 8f42592..fe9ee48 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,18 +3,25 @@ from fastapi import status from fastapi.testclient import TestClient from httpx import Response - -from main import app +from main import rosa from src.core.config.api import APIConfig -client = TestClient(app) +client = TestClient(rosa) def test_home_endpoint() -> None: - """Test case.""" + """Test case for home endpoint.""" response: Response = client.get(f"/{APIConfig.VERSION}") assert response.status_code == status.HTTP_200_OK assert response.headers["content-type"] == "application/json" assert response.headers["content-length"] == "34" assert response.encoding == "utf-8" assert response.json() == {"content": "successful response."} + + +def test_articles_endpoint() -> None: + """Test case for /articles endpoint.""" + response: Response = client.get(f"/{APIConfig.VERSION}/articles") + assert response.status_code == status.HTTP_200_OK + assert response.headers["content-type"] == "application/json" + assert response.encoding == "utf-8" diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..fd031e6 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,112 @@ +"""Unit tests for SQLAlchemy models.""" + +import asyncio + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession +from src.database.connection import engine +from src.cms.models import Article, Base, Category +from src.authentication.models import User + + +@pytest.fixture(scope="module", autouse=True) +def setup_db(): + """Set up and teardown.""" + + async def init_models() -> None: + """Create all tables in the database in asyncmode.""" + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + async def drop_models() -> None: + """Drop all tables in the database in asyncmode.""" + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + asyncio.run(init_models()) + + yield + + asyncio.run(drop_models()) + + +@pytest.fixture +async def db_connection(): + """Database session.""" + async with AsyncSession(engine) as session: + return session + # async with session.begin(): + # # Create a new user + # new_user = User(name="John Doe", email="john@gmail.com") + # session.add(new_user) + + +@pytest.fixture +async def category(db_connection: AsyncSession) -> Category: + """Create category.""" + category = Category(name="sample", description="sample") + async with db_connection.begin(): + db_connection.add(category) + await db_connection.commit() + return category + + +@pytest.fixture +async def user(db_connection: AsyncSession) -> User: + """Create category.""" + user = User(name="pratheesh", email="pratheesh@gmail.com", password_hash="asdasdaq2e") + async with db_connection.begin(): + db_connection.add(user) + await db_connection.commit() + return user + + +@pytest.mark.asyncio +async def test_article_creation( + db_connection: AsyncSession, + category: Category, + user: User, +) -> None: + """Test article creation.""" + category_: Category = category + article_data = { + "title": "Test Article", + "description": "Test article description", + "published_date": "2024-05-01 12:00:00", + "category_id": 1, + "primary_image": "test_image.jpg", + "body": "Test article body", + "is_featured": True, + "slug": "test-article", + } + + article = Article(**article_data) + async with db_connection.begin(): + db_connection.add(article) + await db_connection.flush() + + assert article.id is not None + + +@pytest.mark.asyncio +async def test_article_retrieval(db_connection): + """Test get article.""" + article_data = { + "title": "Test Article", + "description": "Test article description", + "published_date": "2024-05-01 12:00:00", + "category_id": 1, + "primary_image": "test_image.jpg", + "body": "Test article body", + "is_featured": True, + "slug": "test-article", + "user_id": 1, + } + article = Article(**article_data) + db_connection.add(article) + await db_connection.commit() + + fetched_article = await db_connection.get(Article, article.id) + + assert fetched_article is not None + assert fetched_article.title == article.title