Add backend
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*/**/__pycache__
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.sqlite3
|
||||||
|
__pycache__
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ RUN pip install poetry
|
|||||||
|
|
||||||
COPY ./pyproject.toml /app/pyproject.toml
|
COPY ./pyproject.toml /app/pyproject.toml
|
||||||
COPY ./poetry.lock /app/poetry.lock
|
COPY ./poetry.lock /app/poetry.lock
|
||||||
RUN poetry install --only main
|
RUN poetry install --only main --only ui --only backend --all-extras
|
||||||
|
|
||||||
COPY ./soul_diary /app/soul_diary
|
COPY ./soul_diary /app/soul_diary
|
||||||
|
|
||||||
|
|||||||
486
poetry.lock
generated
486
poetry.lock
generated
@@ -1,5 +1,39 @@
|
|||||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiosqlite"
|
||||||
|
version = "0.19.0"
|
||||||
|
description = "asyncio bridge to the standard sqlite3 module"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"},
|
||||||
|
{file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"]
|
||||||
|
docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alembic"
|
||||||
|
version = "1.13.0"
|
||||||
|
description = "A database migration tool for SQLAlchemy."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "alembic-1.13.0-py3-none-any.whl", hash = "sha256:a23974ea301c3ee52705db809c7413cecd165290c6679b9998dd6c74342ca23a"},
|
||||||
|
{file = "alembic-1.13.0.tar.gz", hash = "sha256:ab4b3b94d2e1e5f81e34be8a9b7b7575fc9dd5398fccb0bef351ec9b14872623"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Mako = "*"
|
||||||
|
SQLAlchemy = ">=1.3.0"
|
||||||
|
typing-extensions = ">=4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tz = ["backports.zoneinfo"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -50,6 +84,89 @@ types-python-dateutil = ">=2.8.10"
|
|||||||
doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"]
|
doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"]
|
||||||
test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"]
|
test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asyncpg"
|
||||||
|
version = "0.28.0"
|
||||||
|
description = "An asyncio PostgreSQL driver"
|
||||||
|
optional = true
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a6d1b954d2b296292ddff4e0060f494bb4270d87fb3655dd23c5c6096d16d83"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0740f836985fd2bd73dca42c50c6074d1d61376e134d7ad3ad7566c4f79f8184"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e907cf620a819fab1737f2dd90c0f185e2a796f139ac7de6aa3212a8af96c050"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b339984d55e8202e0c4b252e9573e26e5afa05617ed02252544f7b3e6de3e9"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c402745185414e4c204a02daca3d22d732b37359db4d2e705172324e2d94e85"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c88eef5e096296626e9688f00ab627231f709d0e7e3fb84bb4413dff81d996d7"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-win32.whl", hash = "sha256:90a7bae882a9e65a9e448fdad3e090c2609bb4637d2a9c90bfdcebbfc334bf89"},
|
||||||
|
{file = "asyncpg-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:76aacdcd5e2e9999e83c8fbcb748208b60925cc714a578925adcb446d709016c"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0e08fe2c9b3618459caaef35979d45f4e4f8d4f79490c9fa3367251366af207"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b24e521f6060ff5d35f761a623b0042c84b9c9b9fb82786aadca95a9cb4a893b"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99417210461a41891c4ff301490a8713d1ca99b694fef05dabd7139f9d64bd6c"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f029c5adf08c47b10bcdc857001bbef551ae51c57b3110964844a9d79ca0f267"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d6abf6c2f5152f46fff06b0e74f25800ce8ec6c80967f0bc789974de3c652"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d7fa81ada2807bc50fea1dc741b26a4e99258825ba55913b0ddbf199a10d69d8"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-win32.whl", hash = "sha256:f33c5685e97821533df3ada9384e7784bd1e7865d2b22f153f2e4bd4a083e102"},
|
||||||
|
{file = "asyncpg-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:5e7337c98fb493079d686a4a6965e8bcb059b8e1b8ec42106322fc6c1c889bb0"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1c56092465e718a9fdcc726cc3d9dcf3a692e4834031c9a9f871d92a75d20d48"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4acd6830a7da0eb4426249d71353e8895b350daae2380cb26d11e0d4a01c5472"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63861bb4a540fa033a56db3bb58b0c128c56fad5d24e6d0a8c37cb29b17c1c7d"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a93a94ae777c70772073d0512f21c74ac82a8a49be3a1d982e3f259ab5f27307"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d14681110e51a9bc9c065c4e7944e8139076a778e56d6f6a306a26e740ed86d2"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-win32.whl", hash = "sha256:8aec08e7310f9ab322925ae5c768532e1d78cfb6440f63c078b8392a38aa636a"},
|
||||||
|
{file = "asyncpg-0.28.0-cp37-cp37m-win_amd64.whl", hash = "sha256:319f5fa1ab0432bc91fb39b3960b0d591e6b5c7844dafc92c79e3f1bff96abef"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b337ededaabc91c26bf577bfcd19b5508d879c0ad009722be5bb0a9dd30b85a0"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d32b680a9b16d2957a0a3cc6b7fa39068baba8e6b728f2e0a148a67644578f4"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f62f04cdf38441a70f279505ef3b4eadf64479b17e707c950515846a2df197"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f20cac332c2576c79c2e8e6464791c1f1628416d1115935a34ddd7121bfc6a4"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:59f9712ce01e146ff71d95d561fb68bd2d588a35a187116ef05028675462d5ed"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9e9f9ff1aa0eddcc3247a180ac9e9b51a62311e988809ac6152e8fb8097756"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-win32.whl", hash = "sha256:9e721dccd3838fcff66da98709ed884df1e30a95f6ba19f595a3706b4bc757e3"},
|
||||||
|
{file = "asyncpg-0.28.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ba7d06a0bea539e0487234511d4adf81dc8762249858ed2a580534e1720db00"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d009b08602b8b18edef3a731f2ce6d3f57d8dac2a0a4140367e194eabd3de457"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec46a58d81446d580fb21b376ec6baecab7288ce5a578943e2fc7ab73bf7eb39"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b48ceed606cce9e64fd5480a9b0b9a95cea2b798bb95129687abd8599c8b019"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8858f713810f4fe67876728680f42e93b7e7d5c7b61cf2118ef9153ec16b9423"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5e18438a0730d1c0c1715016eacda6e9a505fc5aa931b37c97d928d44941b4bf"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e9c433f6fcdd61c21a715ee9128a3ca48be8ac16fa07be69262f016bb0f4dbd2"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-win32.whl", hash = "sha256:41e97248d9076bc8e4849da9e33e051be7ba37cd507cbd51dfe4b2d99c70e3dc"},
|
||||||
|
{file = "asyncpg-0.28.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ed77f00c6aacfe9d79e9eff9e21729ce92a4b38e80ea99a58ed382f42ebd55b"},
|
||||||
|
{file = "asyncpg-0.28.0.tar.gz", hash = "sha256:7252cdc3acb2f52feaa3664280d3bcd78a46bd6c10bfd681acfffefa1120e278"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[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 (>=5.0,<6.0)", "uvloop (>=0.15.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "4.1.1"
|
||||||
|
description = "Modern password hashing for your software and your servers"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:196008d91201bbb1aa4e666fee5e610face25d532e433a560cabb33bfdff958b"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-macosx_13_0_universal2.whl", hash = "sha256:2e197534c884336f9020c1f3a8efbaab0aa96fc798068cb2da9c671818b7fbb0"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d573885b637815a7f3a3cd5f87724d7d0822da64b0ab0aa7f7c78bae534e86dc"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab33473f973e8058d1b2df8d6e095d237c49fbf7a02b527541a86a5d1dc4444"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fb931cd004a7ad36a89789caf18a54c20287ec1cd62161265344b9c4554fdb2e"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:12f40f78dcba4aa7d1354d35acf45fae9488862a4fb695c7eeda5ace6aae273f"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2ade10e8613a3b8446214846d3ddbd56cfe9205a7d64742f0b75458c868f7492"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f33b385c3e80b5a26b3a5e148e6165f873c1c202423570fdf45fe34e00e5f3e5"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:755b9d27abcab678e0b8fb4d0abdebeea1f68dd1183b3f518bad8d31fa77d8be"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7a7b8a87e51e5e8ca85b9fdaf3a5dc7aaf123365a09be7a27883d54b9a0c403"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-win32.whl", hash = "sha256:3d6c4e0d6963c52f8142cdea428e875042e7ce8c84812d8e5507bd1e42534e07"},
|
||||||
|
{file = "bcrypt-4.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:14d41933510717f98aac63378b7956bbe548986e435df173c841d7f2bd0b2de7"},
|
||||||
|
{file = "bcrypt-4.1.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:24c2ebd287b5b11016f31d506ca1052d068c3f9dc817160628504690376ff050"},
|
||||||
|
{file = "bcrypt-4.1.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:476aa8e8aca554260159d4c7a97d6be529c8e177dbc1d443cb6b471e24e82c74"},
|
||||||
|
{file = "bcrypt-4.1.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12611c4b0a8b1c461646228344784a1089bc0c49975680a2f54f516e71e9b79e"},
|
||||||
|
{file = "bcrypt-4.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6450538a0fc32fb7ce4c6d511448c54c4ff7640b2ed81badf9898dcb9e5b737"},
|
||||||
|
{file = "bcrypt-4.1.1.tar.gz", hash = "sha256:df37f5418d4f1cdcff845f60e747a015389fa4e63703c918330865e06ad80007"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||||
|
typecheck = ["mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "binaryornot"
|
name = "binaryornot"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -353,6 +470,77 @@ flet-core = "0.14.0"
|
|||||||
httpx = ">=0.24.1,<0.25.0"
|
httpx = ">=0.24.1,<0.25.0"
|
||||||
oauthlib = ">=3.2.2,<4.0.0"
|
oauthlib = ">=3.2.2,<4.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.0.2"
|
||||||
|
description = "Lightweight in-process concurrent programming"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9acd8fd67c248b8537953cb3af8787c18a87c33d4dcf6830e410ee1f95a63fd4"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:339c0272a62fac7e602e4e6ec32a64ff9abadc638b72f17f6713556ed011d493"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38878744926cec29b5cc3654ef47f3003f14bfbba7230e3c8492393fe29cc28b"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3f0497db77cfd034f829678b28267eeeeaf2fc21b3f5041600f7617139e6773"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1a8a08de7f68506a38f9a2ddb26bbd1480689e66d788fcd4b5f77e2d9ecfcc"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89a6f6ddcbef4000cda7e205c4c20d319488ff03db961d72d4e73519d2465309"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1f647fe5b94b51488b314c82fdda10a8756d650cee8d3cd29f657c6031bdf73"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9560c580c896030ff9c311c603aaf2282234643c90d1dec738a1d93e3e53cd51"},
|
||||||
|
{file = "greenlet-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2e9c5423046eec21f6651268cb674dfba97280701e04ef23d312776377313206"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1fd25dfc5879a82103b3d9e43fa952e3026c221996ff4d32a9c72052544835d"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfdc950dd25f25d6582952e58521bca749cf3eeb7a9bad69237024308c8196"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edf7a1daba1f7c54326291a8cde58da86ab115b78c91d502be8744f0aa8e3ffa"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4cf532bf3c58a862196b06947b1b5cc55503884f9b63bf18582a75228d9950e"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e79fb5a9fb2d0bd3b6573784f5e5adabc0b0566ad3180a028af99523ce8f6138"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:006c1028ac0cfcc4e772980cfe73f5476041c8c91d15d64f52482fc571149d46"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fefd5eb2c0b1adffdf2802ff7df45bfe65988b15f6b972706a0e55d451bffaea"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c0fdb8142742ee68e97c106eb81e7d3e883cc739d9c5f2b28bc38a7bafeb6d1"},
|
||||||
|
{file = "greenlet-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:8f8d14a0a4e8c670fbce633d8b9a1ee175673a695475acd838e372966845f764"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:654b84c9527182036747938b81938f1d03fb8321377510bc1854a9370418ab66"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bc4fde0842ff2b9cf33382ad0b4db91c2582db836793d58d174c569637144"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27b142a9080bdd5869a2fa7ebf407b3c0b24bd812db925de90e9afe3c417fd6"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0df7eed98ea23b20e9db64d46eb05671ba33147df9405330695bcd81a73bb0c9"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5d60805057d8948065338be6320d35e26b0a72f45db392eb32b70dd6dc9227"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0e28f5233d64c693382f66d47c362b72089ebf8ac77df7e12ac705c9fa1163d"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e4bfa752b3688d74ab1186e2159779ff4867644d2b1ebf16db14281f0445377"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c42bb589e6e9f9d8bdd79f02f044dff020d30c1afa6e84c0b56d1ce8a324553c"},
|
||||||
|
{file = "greenlet-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b2cedf279ca38ef3f4ed0d013a6a84a7fc3d9495a716b84a5fc5ff448965f251"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:6d65bec56a7bc352bcf11b275b838df618651109074d455a772d3afe25390b7d"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0acadbc3f72cb0ee85070e8d36bd2a4673d2abd10731ee73c10222cf2dd4713c"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14b5d999aefe9ffd2049ad19079f733c3aaa426190ffecadb1d5feacef8fe397"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f27aa32466993c92d326df982c4acccd9530fe354e938d9e9deada563e71ce76"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f34a765c5170c0673eb747213a0275ecc749ab3652bdbec324621ed5b2edaef"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:520fcb53a39ef90f5021c77606952dbbc1da75d77114d69b8d7bded4a8e1a813"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1fceb5351ab1601903e714c3028b37f6ea722be6873f46e349a960156c05650"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7363756cc439a503505b67983237d1cc19139b66488263eb19f5719a32597836"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-win32.whl", hash = "sha256:d5547b462b8099b84746461e882a3eb8a6e3f80be46cb6afb8524eeb191d1a30"},
|
||||||
|
{file = "greenlet-3.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:950e21562818f9c771989b5b65f990e76f4ac27af66e1bb34634ae67886ede2a"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d64643317e76b4b41fdba659e7eca29634e5739b8bc394eda3a9127f697ed4b0"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f9ea7c2c9795549653b6f7569f6bc75d2c7d1f6b2854eb8ce0bc6ec3cb2dd88"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db4233358d3438369051a2f290f1311a360d25c49f255a6c5d10b5bcb3aa2b49"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bf77b41798e8417657245b9f3649314218a4a17aefb02bb3992862df32495"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d0df07a38e41a10dfb62c6fc75ede196572b580f48ee49b9282c65639f3965"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10d247260db20887ae8857c0cbc750b9170f0b067dd7d38fb68a3f2334393bd3"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a37ae53cca36823597fd5f65341b6f7bac2dd69ecd6ca01334bb795460ab150b"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80d068e4b6e2499847d916ef64176811ead6bf210a610859220d537d935ec6fd"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-win32.whl", hash = "sha256:b1405614692ac986490d10d3e1a05e9734f473750d4bee3cf7d1286ef7af7da6"},
|
||||||
|
{file = "greenlet-3.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8756a94ed8f293450b0e91119eca2a36332deba69feb2f9ca410d35e74eae1e4"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2c93cd03acb1499ee4de675e1a4ed8eaaa7227f7949dc55b37182047b006a7aa"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dac09e3c0b78265d2e6d3cbac2d7c48bd1aa4b04a8ffeda3adde9f1688df2c3"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee59c4627c8c4bb3e15949fbcd499abd6b7f4ad9e0bfcb62c65c5e2cabe0ec4"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18fe39d70d482b22f0014e84947c5aaa7211fb8e13dc4cc1c43ed2aa1db06d9a"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84bef3cfb6b6bfe258c98c519811c240dbc5b33a523a14933a252e486797c90"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aecea0442975741e7d69daff9b13c83caff8c13eeb17485afa65f6360a045765"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f260e6c2337871a52161824058923df2bbddb38bc11a5cbe71f3474d877c5bd9"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fc14dd9554f88c9c1fe04771589ae24db76cd56c8f1104e4381b383d6b71aff8"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-win32.whl", hash = "sha256:bfcecc984d60b20ffe30173b03bfe9ba6cb671b0be1e95c3e2056d4fe7006590"},
|
||||||
|
{file = "greenlet-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:c235131bf59d2546bb3ebaa8d436126267392f2e51b85ff45ac60f3a26549af0"},
|
||||||
|
{file = "greenlet-3.0.2.tar.gz", hash = "sha256:1c1129bc47266d83444c85a8e990ae22688cf05fb20d7951fd2866007c2ba9bc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["Sphinx"]
|
||||||
|
test = ["objgraph", "psutil"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -436,6 +624,25 @@ MarkupSafe = ">=2.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
i18n = ["Babel (>=2.7)"]
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mako"
|
||||||
|
version = "1.3.0"
|
||||||
|
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "Mako-1.3.0-py3-none-any.whl", hash = "sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9"},
|
||||||
|
{file = "Mako-1.3.0.tar.gz", hash = "sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=0.9.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
babel = ["Babel"]
|
||||||
|
lingua = ["lingua"]
|
||||||
|
testing = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@@ -540,6 +747,89 @@ files = [
|
|||||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.0.4"
|
||||||
|
description = "multidict implementation"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
|
||||||
|
{file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
|
||||||
|
{file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
|
||||||
|
{file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
|
||||||
|
{file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
|
||||||
|
{file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
|
||||||
|
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oauthlib"
|
name = "oauthlib"
|
||||||
version = "3.2.2"
|
version = "3.2.2"
|
||||||
@@ -987,6 +1277,93 @@ files = [
|
|||||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlalchemy"
|
||||||
|
version = "2.0.23"
|
||||||
|
description = "Database Abstraction Library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-win32.whl", hash = "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-win32.whl", hash = "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"},
|
||||||
|
{file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"},
|
||||||
|
{file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
|
||||||
|
typing-extensions = ">=4.2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
|
||||||
|
aioodbc = ["aioodbc", "greenlet (!=0.4.17)"]
|
||||||
|
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
|
||||||
|
asyncio = ["greenlet (!=0.4.17)"]
|
||||||
|
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
|
||||||
|
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
|
||||||
|
mssql = ["pyodbc"]
|
||||||
|
mssql-pymssql = ["pymssql"]
|
||||||
|
mssql-pyodbc = ["pyodbc"]
|
||||||
|
mypy = ["mypy (>=0.910)"]
|
||||||
|
mysql = ["mysqlclient (>=1.4.0)"]
|
||||||
|
mysql-connector = ["mysql-connector-python"]
|
||||||
|
oracle = ["cx-oracle (>=8)"]
|
||||||
|
oracle-oracledb = ["oracledb (>=1.0.1)"]
|
||||||
|
postgresql = ["psycopg2 (>=2.7)"]
|
||||||
|
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
|
||||||
|
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
|
||||||
|
postgresql-psycopg = ["psycopg (>=3.0.7)"]
|
||||||
|
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||||
|
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||||
|
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
|
||||||
|
pymysql = ["pymysql"]
|
||||||
|
sqlcipher = ["sqlcipher3-binary"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
version = "0.27.0"
|
version = "0.27.0"
|
||||||
@@ -1226,7 +1603,114 @@ files = [
|
|||||||
{file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
|
{file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.9.4"
|
||||||
|
description = "Yet another URL library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
|
||||||
|
{file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
|
||||||
|
{file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
|
||||||
|
{file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
|
||||||
|
{file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
|
||||||
|
{file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
|
||||||
|
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
|
||||||
|
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.0"
|
||||||
|
multidict = ">=4.0"
|
||||||
|
|
||||||
|
[extras]
|
||||||
|
postgres = ["asyncpg"]
|
||||||
|
sqlite = ["aiosqlite"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "10427d6e72deac4ef97a9541f0be6c56cf626d1547ba12258fc3af5e4087e728"
|
content-hash = "2edc77f8a1e7fc20a3330be55ee660506982acfa0ace143d3b5fc41c3b813e5d"
|
||||||
|
|||||||
@@ -9,17 +9,35 @@ include = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
# extras
|
||||||
|
aiosqlite = { version = "^0.19.0", optional = true }
|
||||||
|
asyncpg = { version = "^0.28.0", optional = true }
|
||||||
|
|
||||||
|
# main
|
||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
flet = "^0.14.0"
|
|
||||||
flet-fastapi = "^0.14.0"
|
|
||||||
uvicorn = "^0.24.0.post1"
|
uvicorn = "^0.24.0.post1"
|
||||||
facet = "^0.9.1"
|
facet = "^0.9.1"
|
||||||
flet-route = "^0.3.2"
|
|
||||||
pydantic = "^2.5.2"
|
pydantic = "^2.5.2"
|
||||||
fastapi = "0.101.1"
|
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
pydantic-settings = "^2.1.0"
|
pydantic-settings = "^2.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.extras]
|
||||||
|
sqlite = ["aiosqlite"]
|
||||||
|
postgres = ["asyncpg"]
|
||||||
|
|
||||||
|
[tool.poetry.group.backend.dependencies]
|
||||||
|
fastapi = "0.101.1"
|
||||||
|
sqlalchemy = "^2.0.23"
|
||||||
|
alembic = "^1.13.0"
|
||||||
|
bcrypt = "^4.1.1"
|
||||||
|
|
||||||
|
[tool.poetry.group.ui.dependencies]
|
||||||
|
flet = "^0.14.0"
|
||||||
|
flet-route = "^0.3.2"
|
||||||
|
flet-fastapi = "^0.14.0"
|
||||||
pycryptodomex = "^3.19.0"
|
pycryptodomex = "^3.19.0"
|
||||||
|
yarl = "^1.9.4"
|
||||||
|
httpx = "0.24.1"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|||||||
2
soul_diary/backend/__init__.py
Normal file
2
soul_diary/backend/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .cli import get_cli
|
||||||
|
from .service import BackendService, get_service
|
||||||
7
soul_diary/backend/__main__.py
Normal file
7
soul_diary/backend/__main__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from .cli import get_cli
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli = get_cli()
|
||||||
|
|
||||||
|
cli()
|
||||||
2
soul_diary/backend/api/__init__.py
Normal file
2
soul_diary/backend/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .cli import get_cli
|
||||||
|
from .service import APIService, get_service
|
||||||
13
soul_diary/backend/api/cli.py
Normal file
13
soul_diary/backend/api/cli.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import typer
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli() -> typer.Typer:
|
||||||
|
cli = typer.Typer()
|
||||||
|
|
||||||
|
cli.command(name="run")(run)
|
||||||
|
|
||||||
|
return cli
|
||||||
31
soul_diary/backend/api/dependencies.py
Normal file
31
soul_diary/backend/api/dependencies.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import fastapi
|
||||||
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
|
|
||||||
|
from soul_diary.backend.api.exceptions import HTTPNotAuthenticated
|
||||||
|
from soul_diary.backend.api.settings import APISettings
|
||||||
|
from soul_diary.backend.database import DatabaseService
|
||||||
|
from soul_diary.backend.database.models import Session
|
||||||
|
|
||||||
|
|
||||||
|
async def database(request: fastapi.Request) -> DatabaseService:
|
||||||
|
return request.app.service.database
|
||||||
|
|
||||||
|
|
||||||
|
async def settings(request: fastapi.Request) -> APISettings:
|
||||||
|
return request.app.service.settings
|
||||||
|
|
||||||
|
|
||||||
|
async def is_auth(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
credentials: HTTPAuthorizationCredentials = fastapi.Depends(HTTPBearer()),
|
||||||
|
) -> Session:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
user_session = await database.get_user_session(
|
||||||
|
session=session,
|
||||||
|
token=credentials.credentials,
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_session is None:
|
||||||
|
raise HTTPNotAuthenticated()
|
||||||
|
|
||||||
|
return user_session
|
||||||
41
soul_diary/backend/api/exceptions.py
Normal file
41
soul_diary/backend/api/exceptions.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRegistrationNotSupported(fastapi.HTTPException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
status_code=fastapi.status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Registration not supported.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPUserAlreadyExists(fastapi.HTTPException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
status_code=fastapi.status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="User already exists.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotAuthenticated(fastapi.HTTPException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Not authenticated.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPForbidden(fastapi.HTTPException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
status_code=fastapi.status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Forbidden.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotFound(fastapi.HTTPException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
status_code=fastapi.status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Not found.",
|
||||||
|
)
|
||||||
63
soul_diary/backend/api/handlers.py
Normal file
63
soul_diary/backend/api/handlers.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import fastapi
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
|
from soul_diary.backend.api.settings import APISettings
|
||||||
|
from soul_diary.backend.api.dependencies import database, is_auth, settings
|
||||||
|
from soul_diary.backend.database import DatabaseService
|
||||||
|
from soul_diary.backend.database.models import Session
|
||||||
|
from .exceptions import (
|
||||||
|
HTTPNotAuthenticated,
|
||||||
|
HTTPRegistrationNotSupported,
|
||||||
|
HTTPUserAlreadyExists,
|
||||||
|
)
|
||||||
|
from .schemas import CredentialsRequest, OptionsResponse, TokenResponse
|
||||||
|
|
||||||
|
|
||||||
|
async def options(settings: APISettings = fastapi.Depends(settings)) -> OptionsResponse:
|
||||||
|
return OptionsResponse(registration_enabled=settings.registration_enabled)
|
||||||
|
|
||||||
|
|
||||||
|
async def sign_up(
|
||||||
|
data: CredentialsRequest = fastapi.Body(...),
|
||||||
|
settings: APISettings = fastapi.Depends(settings),
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
) -> TokenResponse:
|
||||||
|
if not settings.registration_enabled:
|
||||||
|
raise HTTPRegistrationNotSupported()
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
user = await database.create_user(
|
||||||
|
session=session,
|
||||||
|
username=data.username,
|
||||||
|
password=data.password,
|
||||||
|
)
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPUserAlreadyExists()
|
||||||
|
user_session = user.sessions[0]
|
||||||
|
|
||||||
|
return TokenResponse(token=user_session.token)
|
||||||
|
|
||||||
|
|
||||||
|
async def sign_in(
|
||||||
|
data: CredentialsRequest = fastapi.Body(...),
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
) -> TokenResponse:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
user_session = await database.auth_user(
|
||||||
|
session=session,
|
||||||
|
username=data.username,
|
||||||
|
password=data.password,
|
||||||
|
)
|
||||||
|
if user_session is None:
|
||||||
|
raise HTTPNotAuthenticated()
|
||||||
|
|
||||||
|
return TokenResponse(token=user_session.token)
|
||||||
|
|
||||||
|
|
||||||
|
async def logout(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
user_session: Session = fastapi.Depends(is_auth),
|
||||||
|
):
|
||||||
|
async with database.transaction() as session:
|
||||||
|
await database.logout_user(session=session, user_session=user_session)
|
||||||
15
soul_diary/backend/api/router.py
Normal file
15
soul_diary/backend/api/router.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
from . import handlers, senses
|
||||||
|
from .dependencies import database
|
||||||
|
|
||||||
|
|
||||||
|
router = fastapi.APIRouter(
|
||||||
|
dependencies=[fastapi.Depends(database)],
|
||||||
|
)
|
||||||
|
|
||||||
|
router.add_api_route(path="/signup", methods=["POST"], endpoint=handlers.sign_up)
|
||||||
|
router.add_api_route(path="/signin", methods=["POST"], endpoint=handlers.sign_in)
|
||||||
|
router.add_api_route(path="/logout", methods=["POST"], endpoint=handlers.logout)
|
||||||
|
router.add_api_route(path="/options", methods=["GET"], endpoint=handlers.options)
|
||||||
|
router.include_router(senses.router, prefix="/senses", tags=["Senses"])
|
||||||
14
soul_diary/backend/api/schemas.py
Normal file
14
soul_diary/backend/api/schemas.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from pydantic import BaseModel, constr
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialsRequest(BaseModel):
|
||||||
|
username: constr(min_length=4, max_length=64, strip_whitespace=True)
|
||||||
|
password: constr(min_length=8, max_length=64)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenResponse(BaseModel):
|
||||||
|
token: constr(min_length=32, max_length=32)
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsResponse(BaseModel):
|
||||||
|
registration_enabled: bool
|
||||||
1
soul_diary/backend/api/senses/__init__.py
Normal file
1
soul_diary/backend/api/senses/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .router import router
|
||||||
24
soul_diary/backend/api/senses/dependencies.py
Normal file
24
soul_diary/backend/api/senses/dependencies.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
from soul_diary.backend.api.dependencies import database, is_auth
|
||||||
|
from soul_diary.backend.api.exceptions import HTTPForbidden, HTTPNotFound
|
||||||
|
from soul_diary.backend.database import DatabaseService
|
||||||
|
from soul_diary.backend.database.models import Sense, Session
|
||||||
|
|
||||||
|
|
||||||
|
async def sense(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
user_session: Session = fastapi.Depends(is_auth),
|
||||||
|
sense_id: uuid.UUID = fastapi.Path(),
|
||||||
|
) -> Sense:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
sense = await database.get_sense(session=session, sense_id=sense_id)
|
||||||
|
|
||||||
|
if sense is None:
|
||||||
|
raise HTTPNotFound()
|
||||||
|
if sense.user_id != user_session.user_id:
|
||||||
|
raise HTTPForbidden()
|
||||||
|
|
||||||
|
return sense
|
||||||
71
soul_diary/backend/api/senses/handlers.py
Normal file
71
soul_diary/backend/api/senses/handlers.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
from soul_diary.backend.api.dependencies import database
|
||||||
|
from soul_diary.backend.database import DatabaseService
|
||||||
|
from soul_diary.backend.database.models import Sense, Session
|
||||||
|
from .dependencies import is_auth, sense
|
||||||
|
from .schemas import (
|
||||||
|
CreateSenseRequest,
|
||||||
|
Pagination,
|
||||||
|
SenseListResponse,
|
||||||
|
SenseResponse,
|
||||||
|
UpdateSenseRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_sense_list(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
user_session: Session = fastapi.Depends(is_auth),
|
||||||
|
pagination: Pagination = fastapi.Depends(Pagination),
|
||||||
|
) -> SenseListResponse:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
senses = await database.get_sense_list(
|
||||||
|
session=session,
|
||||||
|
user=user_session.user,
|
||||||
|
page=pagination.page,
|
||||||
|
limit=pagination.limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
return SenseListResponse(data=senses)
|
||||||
|
|
||||||
|
|
||||||
|
async def create_sense(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
user_session: Session = fastapi.Depends(is_auth),
|
||||||
|
data: CreateSenseRequest = fastapi.Body(),
|
||||||
|
) -> SenseResponse:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
sense = await database.create_sense(
|
||||||
|
session=session,
|
||||||
|
user=user_session.user,
|
||||||
|
data=data.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return SenseResponse.model_validate(sense)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_sense(sense: Sense = fastapi.Depends(sense)) -> SenseResponse:
|
||||||
|
return SenseResponse.model_validate(sense)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_sense(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
sense: Sense = fastapi.Depends(sense),
|
||||||
|
data: UpdateSenseRequest = fastapi.Body(),
|
||||||
|
) -> SenseResponse:
|
||||||
|
async with database.transaction() as session:
|
||||||
|
sense = await database.update_sense(
|
||||||
|
session=session,
|
||||||
|
sense=sense,
|
||||||
|
data=data.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return SenseResponse.model_validate(sense)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_sense(
|
||||||
|
database: DatabaseService = fastapi.Depends(database),
|
||||||
|
sense: Sense = fastapi.Depends(sense),
|
||||||
|
):
|
||||||
|
async with database.transaction() as session:
|
||||||
|
await database.delete_sense(session=session, sense=sense)
|
||||||
11
soul_diary/backend/api/senses/router.py
Normal file
11
soul_diary/backend/api/senses/router.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
from . import handlers
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
router.add_api_route(path="/", methods=["GET"], endpoint=handlers.get_sense_list)
|
||||||
|
router.add_api_route(path="/", methods=["POST"], endpoint=handlers.create_sense)
|
||||||
|
router.add_api_route(path="/{sense_id}", methods=["GET"], endpoint=handlers.get_sense)
|
||||||
|
router.add_api_route(path="/{sense_id}", methods=["POST"], endpoint=handlers.update_sense)
|
||||||
|
router.add_api_route(path="/{sense_id}", methods=["DELETE"], endpoint=handlers.delete_sense)
|
||||||
29
soul_diary/backend/api/senses/schemas.py
Normal file
29
soul_diary/backend/api/senses/schemas.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import AwareDatetime, BaseModel, ConfigDict, PositiveInt
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSenseRequest(BaseModel):
|
||||||
|
data: str
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateSenseRequest(BaseModel):
|
||||||
|
data: str
|
||||||
|
|
||||||
|
|
||||||
|
class SenseResponse(BaseModel):
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
id: uuid.UUID
|
||||||
|
data: str
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Pagination(BaseModel):
|
||||||
|
page: PositiveInt = 1
|
||||||
|
limit: PositiveInt = 10
|
||||||
|
|
||||||
|
|
||||||
|
class SenseListResponse(BaseModel):
|
||||||
|
data: list[SenseResponse]
|
||||||
62
soul_diary/backend/api/service.py
Normal file
62
soul_diary/backend/api/service.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import fastapi
|
||||||
|
import uvicorn
|
||||||
|
from facet import ServiceMixin
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from soul_diary.backend.database import DatabaseService, get_service as get_database_service
|
||||||
|
from . import router
|
||||||
|
from .settings import APISettings
|
||||||
|
|
||||||
|
|
||||||
|
class UvicornServer(uvicorn.Server):
|
||||||
|
def install_signal_handlers(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class APIService(ServiceMixin):
|
||||||
|
def __init__(self, database: DatabaseService, settings: APISettings, port: int = 8001):
|
||||||
|
self._database = database
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
self._port = port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database(self) -> DatabaseService:
|
||||||
|
return self._database
|
||||||
|
|
||||||
|
@property
|
||||||
|
def settings(self) -> APISettings:
|
||||||
|
return self._settings
|
||||||
|
|
||||||
|
def get_app(self) -> fastapi.FastAPI:
|
||||||
|
app = fastapi.FastAPI()
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
app.service = self
|
||||||
|
self.setup_app(app=app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
def setup_app(self, app: fastapi.FastAPI):
|
||||||
|
app.include_router(router.router)
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
config = uvicorn.Config(app=self.get_app(), host="0.0.0.0", port=self._port)
|
||||||
|
server = UvicornServer(config)
|
||||||
|
|
||||||
|
self.add_task(server.serve())
|
||||||
|
|
||||||
|
|
||||||
|
def get_service() -> APIService:
|
||||||
|
database_service = get_database_service()
|
||||||
|
settings = APISettings()
|
||||||
|
return APIService(
|
||||||
|
database=database_service,
|
||||||
|
settings=settings,
|
||||||
|
port=settings.port,
|
||||||
|
)
|
||||||
10
soul_diary/backend/api/settings.py
Normal file
10
soul_diary/backend/api/settings.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pydantic import conint
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class APISettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(prefix="backend_api_")
|
||||||
|
|
||||||
|
port: conint(ge=1, le=65535) = 8001
|
||||||
|
|
||||||
|
registration_enabled: bool = True
|
||||||
22
soul_diary/backend/cli.py
Normal file
22
soul_diary/backend/cli.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from . import api, database
|
||||||
|
from .service import get_service
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
backend_service = get_service()
|
||||||
|
|
||||||
|
asyncio.run(backend_service.run())
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli() -> typer.Typer:
|
||||||
|
cli = typer.Typer()
|
||||||
|
|
||||||
|
cli.add_typer(api.get_cli(), name="api")
|
||||||
|
cli.add_typer(database.get_cli(), name="database")
|
||||||
|
cli.command(name="run")(run)
|
||||||
|
|
||||||
|
return cli
|
||||||
2
soul_diary/backend/database/__init__.py
Normal file
2
soul_diary/backend/database/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .cli import get_cli
|
||||||
|
from .service import DatabaseService, get_service
|
||||||
67
soul_diary/backend/database/cli.py
Normal file
67
soul_diary/backend/database/cli.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from .service import DatabaseService, get_service
|
||||||
|
|
||||||
|
|
||||||
|
def migrations_list(ctx: typer.Context):
|
||||||
|
database_service: DatabaseService = ctx.obj["database"]
|
||||||
|
|
||||||
|
database_service.show_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def migrations_apply(ctx: typer.Context):
|
||||||
|
database_service: DatabaseService = ctx.obj["database"]
|
||||||
|
|
||||||
|
database_service.migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def migrations_rollback(
|
||||||
|
ctx: typer.Context,
|
||||||
|
revision: Optional[str] = typer.Argument(
|
||||||
|
None,
|
||||||
|
help="Revision id or relative revision (`-1`, `-2`)",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
database_service: DatabaseService = ctx.obj["database"]
|
||||||
|
|
||||||
|
database_service.rollback(revision=revision)
|
||||||
|
|
||||||
|
|
||||||
|
def migrations_create(
|
||||||
|
ctx: typer.Context,
|
||||||
|
message: Optional[str] = typer.Option(
|
||||||
|
None,
|
||||||
|
"-m", "--message",
|
||||||
|
help="Migration short message",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
database_service: DatabaseService = ctx.obj["database"]
|
||||||
|
|
||||||
|
database_service.create_migration(message=message)
|
||||||
|
|
||||||
|
|
||||||
|
def get_migrations_cli() -> typer.Typer:
|
||||||
|
cli = typer.Typer(name="Migration")
|
||||||
|
|
||||||
|
cli.command(name="apply")(migrations_apply)
|
||||||
|
cli.command(name="rollback")(migrations_rollback)
|
||||||
|
cli.command(name="create")(migrations_create)
|
||||||
|
cli.command(name="list")(migrations_list)
|
||||||
|
|
||||||
|
return cli
|
||||||
|
|
||||||
|
|
||||||
|
def service_callback(ctx: typer.Context):
|
||||||
|
ctx.obj = ctx.obj or {}
|
||||||
|
ctx.obj["database"] = get_service()
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli() -> typer.Typer:
|
||||||
|
cli = typer.Typer()
|
||||||
|
|
||||||
|
cli.callback()(service_callback)
|
||||||
|
cli.add_typer(get_migrations_cli(), name="migrations")
|
||||||
|
|
||||||
|
return cli
|
||||||
91
soul_diary/backend/database/migrations/env.py
Normal file
91
soul_diary/backend/database/migrations/env.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import asyncio
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import pool
|
||||||
|
from sqlalchemy.engine import Connection
|
||||||
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
from soul_diary.backend.database import models
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
target_metadata = models.Base.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def do_run_migrations(connection: Connection) -> None:
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
async def run_async_migrations() -> None:
|
||||||
|
"""In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
connectable = async_engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
async with connectable.connect() as connection:
|
||||||
|
await connection.run_sync(do_run_migrations)
|
||||||
|
|
||||||
|
await connectable.dispose()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
"""Run migrations in 'online' mode."""
|
||||||
|
|
||||||
|
asyncio.run(run_async_migrations())
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
26
soul_diary/backend/database/migrations/script.py.mako
Normal file
26
soul_diary/backend/database/migrations/script.py.mako
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = ${repr(up_revision)}
|
||||||
|
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||||
|
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""init
|
||||||
|
|
||||||
|
Revision ID: bce1e66bb101
|
||||||
|
Revises:
|
||||||
|
Create Date: 2023-12-14 10:47:06.594959
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'bce1e66bb101'
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=64), nullable=False),
|
||||||
|
sa.Column('password', sa.String(length=72), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('username')
|
||||||
|
)
|
||||||
|
op.create_index('users__id_idx', 'users', ['id'], unique=False, postgresql_using='hash')
|
||||||
|
op.create_index('users__username_idx', 'users', ['username'], unique=False, postgresql_using='hash')
|
||||||
|
op.create_table('senses',
|
||||||
|
sa.Column('id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('data', sa.String(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index('senses__created_at_idx', 'senses', ['created_at'], unique=False, postgresql_using='btree')
|
||||||
|
op.create_index('senses__id_idx', 'senses', ['id'], unique=False, postgresql_using='hash')
|
||||||
|
op.create_index('senses__user_id_idx', 'senses', ['user_id'], unique=False, postgresql_using='btree')
|
||||||
|
op.create_table('sessions',
|
||||||
|
sa.Column('token', sa.String(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('token')
|
||||||
|
)
|
||||||
|
op.create_index('sessions__token_idx', 'sessions', ['token'], unique=False, postgresql_using='hash')
|
||||||
|
op.create_index('sessions__user_id_idx', 'sessions', ['user_id'], unique=False, postgresql_using='btree')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index('sessions__user_id_idx', table_name='sessions', postgresql_using='btree')
|
||||||
|
op.drop_index('sessions__token_idx', table_name='sessions', postgresql_using='hash')
|
||||||
|
op.drop_table('sessions')
|
||||||
|
op.drop_index('senses__user_id_idx', table_name='senses', postgresql_using='btree')
|
||||||
|
op.drop_index('senses__id_idx', table_name='senses', postgresql_using='hash')
|
||||||
|
op.drop_index('senses__created_at_idx', table_name='senses', postgresql_using='btree')
|
||||||
|
op.drop_table('senses')
|
||||||
|
op.drop_index('users__username_idx', table_name='users', postgresql_using='hash')
|
||||||
|
op.drop_index('users__id_idx', table_name='users', postgresql_using='hash')
|
||||||
|
op.drop_table('users')
|
||||||
|
# ### end Alembic commands ###
|
||||||
61
soul_diary/backend/database/models.py
Normal file
61
soul_diary/backend/database/models.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey, Index, String
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id: Mapped[uuid.UUID] = mapped_column(default=uuid.uuid4, primary_key=True)
|
||||||
|
username: Mapped[str] = mapped_column(String(64), unique=True)
|
||||||
|
password: Mapped[str] = mapped_column(String(72))
|
||||||
|
|
||||||
|
senses: Mapped[list["Sense"]] = relationship(back_populates="user")
|
||||||
|
sessions: Mapped[list["Session"]] = relationship(back_populates="user")
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
Index("users__id_idx", "id", postgresql_using="hash"),
|
||||||
|
Index("users__username_idx", "username", postgresql_using="hash"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Session(Base):
|
||||||
|
__tablename__ = "sessions"
|
||||||
|
|
||||||
|
token: Mapped[str] = mapped_column(
|
||||||
|
primary_key=True,
|
||||||
|
default=lambda: "".join(random.choice(string.hexdigits) for _ in range(32)),
|
||||||
|
)
|
||||||
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"))
|
||||||
|
|
||||||
|
user: Mapped[User] = relationship(back_populates="sessions", lazy=False)
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
Index("sessions__token_idx", "token", postgresql_using="hash"),
|
||||||
|
Index("sessions__user_id_idx", "user_id", postgresql_using="btree"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Sense(Base):
|
||||||
|
__tablename__ = "senses"
|
||||||
|
|
||||||
|
id: Mapped[uuid.UUID] = mapped_column(default=uuid.uuid4, primary_key=True)
|
||||||
|
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"))
|
||||||
|
data: Mapped[str]
|
||||||
|
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
||||||
|
|
||||||
|
user: Mapped[User] = relationship(back_populates="senses", lazy=False)
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
Index("senses__id_idx", "id", postgresql_using="hash"),
|
||||||
|
Index("senses__user_id_idx", "user_id", postgresql_using="btree"),
|
||||||
|
Index("senses__created_at_idx", "created_at", postgresql_using="btree"),
|
||||||
|
)
|
||||||
144
soul_diary/backend/database/service.py
Normal file
144
soul_diary/backend/database/service.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import pathlib
|
||||||
|
import uuid
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
from alembic import command as alembic_command
|
||||||
|
from alembic.config import Config as AlembicConfig
|
||||||
|
from facet import ServiceMixin
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
|
from .models import Sense, Session, User
|
||||||
|
from .settings import DatabaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseService(ServiceMixin):
|
||||||
|
def __init__(self, dsn: str):
|
||||||
|
self._dsn = dsn
|
||||||
|
self._engine = create_async_engine(self._dsn, pool_recycle=60)
|
||||||
|
self._sessionmaker = async_sessionmaker(self._engine, expire_on_commit=False)
|
||||||
|
|
||||||
|
def get_alembic_config(self) -> AlembicConfig:
|
||||||
|
migrations_path = pathlib.Path(__file__).parent / "migrations"
|
||||||
|
|
||||||
|
config = AlembicConfig()
|
||||||
|
config.set_main_option("script_location", str(migrations_path))
|
||||||
|
config.set_main_option("sqlalchemy.url", self._dsn.replace("%", "%%"))
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_models(self) -> list[Type[DeclarativeBase]]:
|
||||||
|
return [User, Sense]
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def transaction(self):
|
||||||
|
async with self._sessionmaker() as session:
|
||||||
|
async with session.begin():
|
||||||
|
yield session
|
||||||
|
|
||||||
|
def migrate(self):
|
||||||
|
alembic_command.upgrade(self.get_alembic_config(), "head")
|
||||||
|
|
||||||
|
def rollback(self, revision: str | None = None):
|
||||||
|
revision = revision or "-1"
|
||||||
|
|
||||||
|
alembic_command.downgrade(self.get_alembic_config(), revision)
|
||||||
|
|
||||||
|
def show_migrations(self):
|
||||||
|
alembic_command.history(self.get_alembic_config())
|
||||||
|
|
||||||
|
def create_migration(self, message: str | None = None):
|
||||||
|
alembic_command.revision(
|
||||||
|
self.get_alembic_config(), message=message, autogenerate=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def create_user(self, session: AsyncSession, username: str, password: str) -> User:
|
||||||
|
hashed_password = bcrypt.hashpw(
|
||||||
|
password.encode("utf-8"),
|
||||||
|
bcrypt.gensalt(),
|
||||||
|
).decode("utf-8")
|
||||||
|
user = User(username=username, password=hashed_password)
|
||||||
|
user_session = Session(user=user)
|
||||||
|
user.sessions.append(user_session)
|
||||||
|
|
||||||
|
session.add_all([user, user_session])
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def auth_user(
|
||||||
|
self,
|
||||||
|
session: AsyncSession,
|
||||||
|
username: str,
|
||||||
|
password: str,
|
||||||
|
) -> Session | None:
|
||||||
|
query = select(User).where(User.username == username)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if user is None:
|
||||||
|
return None
|
||||||
|
if not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
|
||||||
|
return None
|
||||||
|
|
||||||
|
user_session = Session(user=user)
|
||||||
|
session.add(user_session)
|
||||||
|
|
||||||
|
return user_session
|
||||||
|
|
||||||
|
async def logout_user(self, session: AsyncSession, user_session: Session):
|
||||||
|
await session.delete(user_session)
|
||||||
|
|
||||||
|
async def get_user_session(self, session: AsyncSession, token: str) -> Session | None:
|
||||||
|
query = select(Session).where(Session.token == token)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
user_session = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
return user_session
|
||||||
|
|
||||||
|
async def get_sense_list(
|
||||||
|
self,
|
||||||
|
session: AsyncSession,
|
||||||
|
user: User,
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 10,
|
||||||
|
) -> list[Sense]:
|
||||||
|
query = select(Sense).where(Sense.user == user).limit(limit).offset((page - 1) * limit)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
senses = result.scalars().all()
|
||||||
|
|
||||||
|
return list(senses)
|
||||||
|
|
||||||
|
async def create_sense(self, session: AsyncSession, user: User, data: str) -> Sense:
|
||||||
|
sense = Sense(user=user, data=data)
|
||||||
|
|
||||||
|
session.add(sense)
|
||||||
|
|
||||||
|
return sense
|
||||||
|
|
||||||
|
async def get_sense(self, session: AsyncSession, sense_id: uuid.UUID) -> Sense | None:
|
||||||
|
query = select(Sense).where(Sense.id == sense_id)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
sense = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
return sense
|
||||||
|
|
||||||
|
async def update_sense(self, session: AsyncSession, sense: Sense, data: str) -> Sense:
|
||||||
|
sense.data = data
|
||||||
|
|
||||||
|
session.add(sense)
|
||||||
|
|
||||||
|
return sense
|
||||||
|
|
||||||
|
async def delete_sense(self, session: AsyncSession, sense: Sense):
|
||||||
|
await session.delete(sense)
|
||||||
|
|
||||||
|
|
||||||
|
def get_service() -> DatabaseService:
|
||||||
|
settings = DatabaseSettings()
|
||||||
|
return DatabaseService(dsn=str(settings.dsn))
|
||||||
8
soul_diary/backend/database/settings.py
Normal file
8
soul_diary/backend/database/settings.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from pydantic import AnyUrl
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseSettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(prefix="backend_database_")
|
||||||
|
|
||||||
|
dsn: AnyUrl = "sqlite+aiosqlite:///soul_diary.sqlite3"
|
||||||
19
soul_diary/backend/service.py
Normal file
19
soul_diary/backend/service.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from facet import ServiceMixin
|
||||||
|
|
||||||
|
from .api import APIService, get_service as get_api_service
|
||||||
|
|
||||||
|
|
||||||
|
class BackendService(ServiceMixin):
|
||||||
|
def __init__(self, api: APIService):
|
||||||
|
self._api = api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dependencies(self) -> list[ServiceMixin]:
|
||||||
|
return [
|
||||||
|
self._api,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_service() -> BackendService:
|
||||||
|
api_service = get_api_service()
|
||||||
|
return BackendService(api=api_service)
|
||||||
@@ -2,18 +2,20 @@ import asyncio
|
|||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
from . import ui
|
from . import backend, ui
|
||||||
|
from .service import get_service
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
ui_service = ui.get_service()
|
soul_diary_service = get_service()
|
||||||
|
|
||||||
asyncio.run(ui_service.run())
|
asyncio.run(soul_diary_service.run())
|
||||||
|
|
||||||
|
|
||||||
def get_cli() -> typer.Typer:
|
def get_cli() -> typer.Typer:
|
||||||
cli = typer.Typer()
|
cli = typer.Typer()
|
||||||
|
|
||||||
|
cli.add_typer(backend.get_cli(), name="backend")
|
||||||
cli.add_typer(ui.get_cli(), name="ui")
|
cli.add_typer(ui.get_cli(), name="ui")
|
||||||
cli.command(name="run")(run)
|
cli.command(name="run")(run)
|
||||||
|
|
||||||
|
|||||||
23
soul_diary/service.py
Normal file
23
soul_diary/service.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from facet import ServiceMixin
|
||||||
|
|
||||||
|
from .backend import BackendService, get_service as get_backend_service
|
||||||
|
from .ui import UIService, get_service as get_ui_service
|
||||||
|
|
||||||
|
|
||||||
|
class SoulDiaryService(ServiceMixin):
|
||||||
|
def __init__(self, backend: BackendService, ui: UIService):
|
||||||
|
self._backend = backend
|
||||||
|
self._ui = ui
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dependencies(self) -> list[ServiceMixin]:
|
||||||
|
return [
|
||||||
|
self._backend,
|
||||||
|
self._ui,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_service() -> SoulDiaryService:
|
||||||
|
backend_service = get_backend_service()
|
||||||
|
ui_service = get_ui_service()
|
||||||
|
return SoulDiaryService(backend=backend_service, ui=ui_service)
|
||||||
@@ -100,9 +100,11 @@ class BaseBackend:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def logout(self):
|
async def logout(self):
|
||||||
|
await self.deauth()
|
||||||
self._token = None
|
self._token = None
|
||||||
self._encryption_key = None
|
self._encryption_key = None
|
||||||
self._username = None
|
self._username = None
|
||||||
|
await self._local_storage.remove_auth_data()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_auth(self) -> bool:
|
def is_auth(self) -> bool:
|
||||||
@@ -167,6 +169,9 @@ class BaseBackend:
|
|||||||
async def auth(self, username: str, password: str) -> str:
|
async def auth(self, username: str, password: str) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def deauth(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
async def get_options(self) -> Options:
|
async def get_options(self) -> Options:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ class BackendException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationNotSupportedException(BackendException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserAlreadyExistsException(BackendException):
|
class UserAlreadyExistsException(BackendException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ class LocalBackend(BaseBackend):
|
|||||||
|
|
||||||
return auth_block
|
return auth_block
|
||||||
|
|
||||||
|
async def deauth(self):
|
||||||
|
pass
|
||||||
|
|
||||||
async def get_options(self) -> Options:
|
async def get_options(self) -> Options:
|
||||||
return Options(registration_enabled=True)
|
return Options(registration_enabled=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import AwareDatetime, BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class SenseBackendData(BaseModel):
|
class SenseBackendData(BaseModel):
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
data: str
|
data: str
|
||||||
created_at: AwareDatetime
|
created_at: datetime
|
||||||
|
|||||||
173
soul_diary/ui/app/backend/soul.py
Normal file
173
soul_diary/ui/app/backend/soul.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import uuid
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import yarl
|
||||||
|
|
||||||
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
|
from soul_diary.ui.app.models import BackendType, Options
|
||||||
|
from .base import BaseBackend
|
||||||
|
from .exceptions import (
|
||||||
|
IncorrectCredentialsException,
|
||||||
|
NonAuthenticatedException,
|
||||||
|
RegistrationNotSupportedException,
|
||||||
|
SenseNotFoundException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
)
|
||||||
|
from .models import SenseBackendData
|
||||||
|
|
||||||
|
|
||||||
|
class SoulBackend(BaseBackend):
|
||||||
|
BACKEND = BackendType.SOUL
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
url: yarl.URL | str,
|
||||||
|
local_storage: LocalStorage,
|
||||||
|
username: str | None = None,
|
||||||
|
encryption_key: str | None = None,
|
||||||
|
token: str | None = None,
|
||||||
|
):
|
||||||
|
self._url = yarl.URL(url)
|
||||||
|
self._client = httpx.AsyncClient()
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
local_storage=local_storage,
|
||||||
|
username=username,
|
||||||
|
encryption_key=encryption_key,
|
||||||
|
token=token,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_backend_data(self) -> dict[str, Any]:
|
||||||
|
return {"url": str(self._url)}
|
||||||
|
|
||||||
|
async def request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
path: str,
|
||||||
|
json = None,
|
||||||
|
params: dict[str, Any] | None = None,
|
||||||
|
):
|
||||||
|
url = self._url / path.lstrip("/")
|
||||||
|
headers = {}
|
||||||
|
if self._token:
|
||||||
|
headers["Authorization"] = f"Bearer {self._token}"
|
||||||
|
|
||||||
|
response = await self._client.request(
|
||||||
|
method=method,
|
||||||
|
url=str(url),
|
||||||
|
json=json,
|
||||||
|
params=params,
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 401:
|
||||||
|
raise NonAuthenticatedException()
|
||||||
|
else:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def create_user(self, username: str, password: str) -> str:
|
||||||
|
path = "/signup"
|
||||||
|
data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.request(method="POST", path=path, json=data)
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
response = exc.response.json()
|
||||||
|
if response == {"detail": "User already exists."}:
|
||||||
|
raise UserAlreadyExistsException()
|
||||||
|
elif response == {"detail": "Registration not supported."}:
|
||||||
|
raise RegistrationNotSupportedException()
|
||||||
|
else:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
return response["token"]
|
||||||
|
|
||||||
|
async def auth(self, username: str, password: str) -> str:
|
||||||
|
path = "/signin"
|
||||||
|
data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.request(method="POST", path=path, json=data)
|
||||||
|
except NonAuthenticatedException:
|
||||||
|
raise IncorrectCredentialsException()
|
||||||
|
|
||||||
|
return response["token"]
|
||||||
|
|
||||||
|
async def deauth(self):
|
||||||
|
path = "/logout"
|
||||||
|
|
||||||
|
await self.request(method="POST", path=path)
|
||||||
|
|
||||||
|
async def get_options(self) -> Options:
|
||||||
|
path = "/options"
|
||||||
|
|
||||||
|
response = await self.request(method="GET", path=path)
|
||||||
|
|
||||||
|
return Options.model_validate(response)
|
||||||
|
|
||||||
|
async def fetch_sense_list(
|
||||||
|
self,
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 10,
|
||||||
|
) -> list[SenseBackendData]:
|
||||||
|
path = "/senses/"
|
||||||
|
params = {"page": page, "limit": limit}
|
||||||
|
|
||||||
|
response = await self.request(method="GET", path=path, params=params)
|
||||||
|
senses = [SenseBackendData.model_validate(sense) for sense in response["data"]]
|
||||||
|
|
||||||
|
return senses
|
||||||
|
|
||||||
|
async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData:
|
||||||
|
path = f"/senses/{sense_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.request(method="GET", path=path)
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 404:
|
||||||
|
raise SenseNotFoundException()
|
||||||
|
else:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
return SenseBackendData.model_validate(response)
|
||||||
|
|
||||||
|
async def pull_sense_data(
|
||||||
|
self,
|
||||||
|
data: str,
|
||||||
|
sense_id: uuid.UUID | None = None,
|
||||||
|
) -> SenseBackendData:
|
||||||
|
path = "/senses/" if sense_id is None else f"/senses/{sense_id}"
|
||||||
|
request_data = {"data": data}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.request(method="POST", path=path, json=request_data)
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 404:
|
||||||
|
raise SenseNotFoundException()
|
||||||
|
else:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
return SenseBackendData.model_validate(response)
|
||||||
|
|
||||||
|
async def delete_sense(self, sense_id: uuid.UUID):
|
||||||
|
path = f"/senses/{sense_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.request(method="DELETE", path=path)
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 404:
|
||||||
|
raise SenseNotFoundException()
|
||||||
|
else:
|
||||||
|
raise exc
|
||||||
@@ -35,9 +35,11 @@ class LocalStorage:
|
|||||||
await self.raw_write("soul_diary.client", auth_data.model_dump(mode="json"))
|
await self.raw_write("soul_diary.client", auth_data.model_dump(mode="json"))
|
||||||
|
|
||||||
async def get_auth_data(self) -> AuthData | None:
|
async def get_auth_data(self) -> AuthData | None:
|
||||||
if await self.raw_contains("soul_diary.client"):
|
if not await self.raw_contains("soul_diary.client"):
|
||||||
data = await self.raw_read("soul_diary.client")
|
return None
|
||||||
return AuthData.model_validate(data)
|
|
||||||
|
data = await self.raw_read("soul_diary.client")
|
||||||
|
return AuthData.model_validate(data)
|
||||||
|
|
||||||
async def remove_auth_data(self):
|
async def remove_auth_data(self):
|
||||||
if await self.raw_contains("soul_diary.client"):
|
if await self.raw_contains("soul_diary.client"):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from soul_diary.ui.app.routes import AUTH, SENSE_LIST
|
|||||||
async def middleware(page: flet.Page, params: Params, basket: Basket):
|
async def middleware(page: flet.Page, params: Params, basket: Basket):
|
||||||
local_storage = LocalStorage(client_storage=page.client_storage)
|
local_storage = LocalStorage(client_storage=page.client_storage)
|
||||||
auth_data = await local_storage.get_auth_data()
|
auth_data = await local_storage.get_auth_data()
|
||||||
|
# await local_storage._client_storage.clear_async()
|
||||||
if auth_data is None:
|
if auth_data is None:
|
||||||
await page.go_async(AUTH)
|
await page.go_async(AUTH)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import enum
|
import enum
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import AwareDatetime, BaseModel, constr
|
from pydantic import BaseModel, constr
|
||||||
|
|
||||||
|
|
||||||
class Emotion(str, enum.Enum):
|
class Emotion(str, enum.Enum):
|
||||||
@@ -24,7 +25,7 @@ class Sense(BaseModel):
|
|||||||
feelings: constr(min_length=1, strip_whitespace=True)
|
feelings: constr(min_length=1, strip_whitespace=True)
|
||||||
body: constr(min_length=1, strip_whitespace=True)
|
body: constr(min_length=1, strip_whitespace=True)
|
||||||
desires: constr(min_length=1, strip_whitespace=True)
|
desires: constr(min_length=1, strip_whitespace=True)
|
||||||
created_at: AwareDatetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
class Options(BaseModel):
|
class Options(BaseModel):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import flet
|
|||||||
from pydantic import AnyHttpUrl
|
from pydantic import AnyHttpUrl
|
||||||
|
|
||||||
from soul_diary.ui.app.backend.exceptions import IncorrectCredentialsException, UserAlreadyExistsException
|
from soul_diary.ui.app.backend.exceptions import IncorrectCredentialsException, UserAlreadyExistsException
|
||||||
from soul_diary.ui.app.backend.local import LocalBackend
|
from soul_diary.ui.app.backend.soul import SoulBackend
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
||||||
from soul_diary.ui.app.models import BackendType, Options
|
from soul_diary.ui.app.models import BackendType, Options
|
||||||
@@ -243,11 +243,16 @@ class AuthView(BaseView):
|
|||||||
|
|
||||||
async def connect_to_soul_server(self) -> Options:
|
async def connect_to_soul_server(self) -> Options:
|
||||||
try:
|
try:
|
||||||
AnyHttpUrl(self.backend_data.get("url"))
|
backend_url = AnyHttpUrl(self.backend_data.get("url"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise SoulServerIncorrectURL()
|
raise SoulServerIncorrectURL()
|
||||||
raise
|
|
||||||
|
backend_client = SoulBackend(
|
||||||
|
local_storage=self.local_storage,
|
||||||
|
url=str(backend_url),
|
||||||
|
)
|
||||||
|
return await backend_client.get_options()
|
||||||
|
|
||||||
async def callback_change_username(self, event: flet.ControlEvent):
|
async def callback_change_username(self, event: flet.ControlEvent):
|
||||||
self.username = event.control.value
|
self.username = event.control.value
|
||||||
|
|
||||||
@@ -268,10 +273,13 @@ class AuthView(BaseView):
|
|||||||
await event.page.update_async()
|
await event.page.update_async()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.backend == BackendType.LOCAL:
|
backend_client_class = self.BACKEND_MAPPING.get(self.backend)
|
||||||
backend_client = LocalBackend(local_storage=self.local_storage)
|
if backend_client_class is None:
|
||||||
else:
|
|
||||||
raise
|
raise
|
||||||
|
backend_client = backend_client_class(
|
||||||
|
local_storage=self.local_storage,
|
||||||
|
**self.backend_data,
|
||||||
|
)
|
||||||
|
|
||||||
async with self.in_progress(page=event.page):
|
async with self.in_progress(page=event.page):
|
||||||
try:
|
try:
|
||||||
@@ -297,10 +305,13 @@ class AuthView(BaseView):
|
|||||||
await event.page.update_async()
|
await event.page.update_async()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.backend == BackendType.LOCAL:
|
backend_client_class = self.BACKEND_MAPPING.get(self.backend)
|
||||||
backend_client = LocalBackend(local_storage=self.local_storage)
|
if backend_client_class is None:
|
||||||
else:
|
|
||||||
raise
|
raise
|
||||||
|
backend_client = backend_client_class(
|
||||||
|
local_storage=self.local_storage,
|
||||||
|
**self.backend_data,
|
||||||
|
)
|
||||||
|
|
||||||
async with self.in_progress(page=event.page):
|
async with self.in_progress(page=event.page):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from flet_route import Basket, Params
|
|||||||
from soul_diary.ui.app.backend.base import BaseBackend
|
from soul_diary.ui.app.backend.base import BaseBackend
|
||||||
from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
|
from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
|
||||||
from soul_diary.ui.app.backend.local import LocalBackend
|
from soul_diary.ui.app.backend.local import LocalBackend
|
||||||
|
from soul_diary.ui.app.backend.soul import SoulBackend
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
||||||
from soul_diary.ui.app.models import BackendType
|
from soul_diary.ui.app.models import BackendType
|
||||||
@@ -67,6 +68,11 @@ class MetaView(type):
|
|||||||
|
|
||||||
|
|
||||||
class BaseView(metaclass=MetaView):
|
class BaseView(metaclass=MetaView):
|
||||||
|
BACKEND_MAPPING = {
|
||||||
|
BackendType.LOCAL: LocalBackend,
|
||||||
|
BackendType.SOUL: SoulBackend,
|
||||||
|
}
|
||||||
|
|
||||||
is_abstract = True
|
is_abstract = True
|
||||||
_initial_view: Callable | None
|
_initial_view: Callable | None
|
||||||
|
|
||||||
@@ -126,14 +132,13 @@ class BaseView(metaclass=MetaView):
|
|||||||
if self._initial_view is not None:
|
if self._initial_view is not None:
|
||||||
await self._initial_view(page=page)
|
await self._initial_view(page=page)
|
||||||
|
|
||||||
async def get_backend_client(self, page: flet.Page) -> BaseBackend:
|
async def get_backend_client(self) -> BaseBackend:
|
||||||
auth_data = await self.local_storage.get_auth_data()
|
auth_data = await self.local_storage.get_auth_data()
|
||||||
if auth_data is None:
|
if auth_data is None:
|
||||||
raise NonAuthenticatedException()
|
raise NonAuthenticatedException()
|
||||||
|
|
||||||
if auth_data.backend == BackendType.LOCAL:
|
backend_client_class = self.BACKEND_MAPPING.get(auth_data.backend, None)
|
||||||
backend_client_class = LocalBackend
|
if backend_client_class is None:
|
||||||
else:
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return backend_client_class(
|
return backend_client_class(
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ class SenseAddView(BaseView):
|
|||||||
await event.page.update_async()
|
await event.page.update_async()
|
||||||
return
|
return
|
||||||
|
|
||||||
backend_client = await self.get_backend_client(page=event.page)
|
backend_client = await self.get_backend_client()
|
||||||
async with self.in_progress(page=event.page):
|
async with self.in_progress(page=event.page):
|
||||||
await backend_client.create_sense(
|
await backend_client.create_sense(
|
||||||
emotions=self.emotions,
|
emotions=self.emotions,
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ class SenseListView(BaseView):
|
|||||||
if auth_data is None:
|
if auth_data is None:
|
||||||
raise NonAuthenticatedException()
|
raise NonAuthenticatedException()
|
||||||
|
|
||||||
if auth_data.backend == BackendType.LOCAL:
|
backend_client = await self.get_backend_client()
|
||||||
pass
|
|
||||||
backend_client = await self.get_backend_client(page=page)
|
|
||||||
senses = await backend_client.get_sense_list()
|
senses = await backend_client.get_sense_list()
|
||||||
self.cards.controls = [await self.render_card_from_sense(sense) for sense in senses]
|
self.cards.controls = [await self.render_card_from_sense(sense) for sense in senses]
|
||||||
await page.update_async()
|
await page.update_async()
|
||||||
@@ -98,5 +96,6 @@ class SenseListView(BaseView):
|
|||||||
await event.page.go_async(SENSE_ADD)
|
await event.page.go_async(SENSE_ADD)
|
||||||
|
|
||||||
async def callback_logout(self, event: flet.ControlEvent):
|
async def callback_logout(self, event: flet.ControlEvent):
|
||||||
await self.local_storage.remove_auth_data()
|
backend_client = await self.get_backend_client()
|
||||||
|
await backend_client.logout()
|
||||||
await event.page.go_async(AUTH)
|
await event.page.go_async(AUTH)
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
from . import web
|
from . import web
|
||||||
|
from .service import get_service
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
ui_service = get_service()
|
||||||
|
|
||||||
|
asyncio.run(ui_service.run())
|
||||||
|
|
||||||
|
|
||||||
def get_cli() -> typer.Typer:
|
def get_cli() -> typer.Typer:
|
||||||
cli = typer.Typer()
|
cli = typer.Typer()
|
||||||
|
|
||||||
|
cli.command(name="run")(run)
|
||||||
cli.add_typer(web.get_cli(), name="web")
|
cli.add_typer(web.get_cli(), name="web")
|
||||||
|
|
||||||
return cli
|
return cli
|
||||||
|
|||||||
@@ -25,9 +25,8 @@ class WebService(ServiceMixin):
|
|||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
app = flet_fastapi.app(SoulDiaryApp(
|
app = flet_fastapi.app(SoulDiaryApp(
|
||||||
# backend=BackendType.SOUL,
|
backend=BackendType.SOUL,
|
||||||
# backend_data=self._backend_data,
|
backend_data=self._backend_data,
|
||||||
backend=BackendType.LOCAL,
|
|
||||||
).run)
|
).run)
|
||||||
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
|
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
|
||||||
server = UvicornServer(config)
|
server = UvicornServer(config)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import conint
|
from pydantic import conint
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class WebSettings(BaseSettings):
|
class WebSettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(env_prefix="ui_web_")
|
||||||
|
|
||||||
port: conint(ge=1, le=65535) = 8000
|
port: conint(ge=1, le=65535) = 8000
|
||||||
backend_data: dict[str, Any] = {
|
backend_data: dict[str, Any] = {
|
||||||
"url": "http://localhost:8001",
|
"url": "http://localhost:8001",
|
||||||
|
|||||||
Reference in New Issue
Block a user