Compare commits
39 Commits
dev
...
2471c2981f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2471c2981f | ||
|
|
9c20b7690c | ||
|
|
6657f55545 | ||
|
|
45ebed40bc | ||
|
|
c8e1863ee6 | ||
|
|
c61db4bc9d | ||
|
|
93cf7b2d24 | ||
|
|
acb3eefcbe | ||
|
|
a6c00e20c3 | ||
|
|
0ab17f3a99 | ||
|
|
22ee887238 | ||
|
|
cdee74210e | ||
|
|
b9a9e4e094 | ||
|
|
9f105c32c6 | ||
|
|
7c334491c3 | ||
|
|
d3eba77444 | ||
|
|
36dad7b441 | ||
|
|
1a495e28c5 | ||
|
|
4789da1b70 | ||
|
|
8ec8639848 | ||
|
|
d639abfbc5 | ||
|
|
d530412805 | ||
|
|
19a9b36173 | ||
|
|
dd6f06cd12 | ||
|
|
a0f9aaeaea | ||
|
|
e5dec473cb | ||
|
|
f3030593da | ||
|
|
d27aafb4f6 | ||
|
|
18b5a573c1 | ||
|
|
b12bb65268 | ||
|
|
61512f70d4 | ||
|
|
31879ddf30 | ||
|
|
b6ec15af29 | ||
|
|
459969f2be | ||
|
|
4de11f4149 | ||
|
|
817a799ef5 | ||
|
|
ef38c45f14 | ||
|
|
1a45e2c284 | ||
|
|
231caf7c6e |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,7 @@
|
|||||||
/.venv/
|
/.venv/
|
||||||
/.idea
|
/.idea
|
||||||
|
__pycache__/
|
||||||
|
*.db
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.vscode/
|
||||||
385
poetry.lock
generated
385
poetry.lock
generated
@@ -74,209 +74,40 @@ test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "except
|
|||||||
trio = ["trio (>=0.26.1)"]
|
trio = ["trio (>=0.26.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2-cffi"
|
name = "bcrypt"
|
||||||
version = "23.1.0"
|
version = "4.0.1"
|
||||||
description = "Argon2 for Python"
|
description = "Modern password hashing for your software and your servers"
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"},
|
|
||||||
{file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
argon2-cffi-bindings = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
dev = ["argon2-cffi[tests,typing]", "tox (>4)"]
|
|
||||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"]
|
|
||||||
tests = ["hypothesis", "pytest"]
|
|
||||||
typing = ["mypy"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "argon2-cffi-bindings"
|
|
||||||
version = "21.2.0"
|
|
||||||
description = "Low-level CFFI bindings for Argon2"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"},
|
{file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"},
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"},
|
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"},
|
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"},
|
{file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"},
|
{file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"},
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"},
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"},
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"},
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"},
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"},
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"},
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"},
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"},
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
|
||||||
{file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"},
|
{file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cffi = ">=1.0.1"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
dev = ["cogapp", "pre-commit", "pytest", "wheel"]
|
|
||||||
tests = ["pytest"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bcrypt"
|
|
||||||
version = "4.3.0"
|
|
||||||
description = "Modern password hashing for your software and your servers"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938"},
|
|
||||||
{file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||||
typecheck = ["mypy"]
|
typecheck = ["mypy"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cffi"
|
|
||||||
version = "1.17.1"
|
|
||||||
description = "Foreign Function Interface for Python calling C code."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
|
|
||||||
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
|
|
||||||
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
|
|
||||||
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
|
|
||||||
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
|
|
||||||
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
|
|
||||||
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
|
|
||||||
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pycparser = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.2.1"
|
version = "8.2.1"
|
||||||
@@ -305,66 +136,6 @@ files = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cryptography"
|
|
||||||
version = "45.0.3"
|
|
||||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-win32.whl", hash = "sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710"},
|
|
||||||
{file = "cryptography-45.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-win32.whl", hash = "sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab"},
|
|
||||||
{file = "cryptography-45.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed43d396f42028c1f47b5fec012e9e12631266e3825e95c00e3cf94d472dac49"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fed5aaca1750e46db870874c9c273cd5182a9e9deb16f06f7bdffdb5c2bde4b9"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00094838ecc7c6594171e8c8a9166124c1197b074cfca23645cee573910d76bc"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:92d5f428c1a0439b2040435a1d6bc1b26ebf0af88b093c3628913dd464d13fa1"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:ec64ee375b5aaa354b2b273c921144a660a511f9df8785e6d1c942967106438e"},
|
|
||||||
{file = "cryptography-45.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:71320fbefd05454ef2d457c481ba9a5b0e540f3753354fff6f780927c25d19b0"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:edd6d51869beb7f0d472e902ef231a9b7689508e83880ea16ca3311a00bf5ce7"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:555e5e2d3a53b4fabeca32835878b2818b3f23966a4efb0d566689777c5a12c8"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:25286aacb947286620a31f78f2ed1a32cded7be5d8b729ba3fb2c988457639e4"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:050ce5209d5072472971e6efbfc8ec5a8f9a841de5a4db0ebd9c2e392cb81972"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dc10ec1e9f21f33420cc05214989544727e776286c1c16697178978327b95c9c"},
|
|
||||||
{file = "cryptography-45.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9eda14f049d7f09c2e8fb411dda17dd6b16a3c76a1de5e249188a32aeb92de19"},
|
|
||||||
{file = "cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""]
|
|
||||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
|
||||||
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""]
|
|
||||||
pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
|
|
||||||
sdist = ["build (>=1.0.0)"]
|
|
||||||
ssh = ["bcrypt (>=3.1.5)"]
|
|
||||||
test = ["certifi (>=2024)", "cryptography-vectors (==45.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
|
||||||
test-randomorder = ["pytest-randomly"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -404,14 +175,14 @@ idna = ">=2.0.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.115.12"
|
version = "0.115.14"
|
||||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"},
|
{file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"},
|
||||||
{file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"},
|
{file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -423,49 +194,6 @@ typing-extensions = ">=4.8.0"
|
|||||||
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastapi-users"
|
|
||||||
version = "14.0.1"
|
|
||||||
description = "Ready-to-use and customizable users management for FastAPI"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.9"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "fastapi_users-14.0.1-py3-none-any.whl", hash = "sha256:074df59676dccf79412d2880bdcb661ab1fabc2ecec1f043b4e6a23be97ed9e1"},
|
|
||||||
{file = "fastapi_users-14.0.1.tar.gz", hash = "sha256:8c032b3a75c6fb2b1f5eab8ffce5321176e9916efe1fe93e7c15ee55f0b02236"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
email-validator = ">=1.1.0,<2.3"
|
|
||||||
fastapi = ">=0.65.2"
|
|
||||||
fastapi-users-db-sqlalchemy = {version = ">=7.0.0", optional = true, markers = "extra == \"sqlalchemy\""}
|
|
||||||
makefun = ">=1.11.2,<2.0.0"
|
|
||||||
pwdlib = {version = "0.2.1", extras = ["argon2", "bcrypt"]}
|
|
||||||
pyjwt = {version = "2.10.1", extras = ["crypto"]}
|
|
||||||
python-multipart = "0.0.20"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
beanie = ["fastapi-users-db-beanie (>=4.0.0)"]
|
|
||||||
oauth = ["httpx-oauth (>=0.13)"]
|
|
||||||
redis = ["redis (>=4.3.3,<6.0.0)"]
|
|
||||||
sqlalchemy = ["fastapi-users-db-sqlalchemy (>=7.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastapi-users-db-sqlalchemy"
|
|
||||||
version = "7.0.0"
|
|
||||||
description = "FastAPI Users database adapter for SQLAlchemy"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.9"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e"},
|
|
||||||
{file = "fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
fastapi-users = ">=10.0.0"
|
|
||||||
sqlalchemy = {version = ">=2.0.0,<2.1.0", extras = ["asyncio"]}
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "3.2.2"
|
version = "3.2.2"
|
||||||
@@ -562,18 +290,6 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "makefun"
|
|
||||||
version = "1.16.0"
|
|
||||||
description = "Small library to dynamically create python functions."
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4"},
|
|
||||||
{file = "makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mako"
|
name = "mako"
|
||||||
version = "1.3.10"
|
version = "1.3.10"
|
||||||
@@ -666,47 +382,33 @@ files = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwdlib"
|
name = "passlib"
|
||||||
version = "0.2.1"
|
version = "1.7.4"
|
||||||
description = "Modern password hashing for Python"
|
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = "*"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pwdlib-0.2.1-py3-none-any.whl", hash = "sha256:1823dc6f22eae472b540e889ecf57fd424051d6a4023ec0bcf7f0de2d9d7ef8c"},
|
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||||
{file = "pwdlib-0.2.1.tar.gz", hash = "sha256:9a1d8a8fa09a2f7ebf208265e55d7d008103cbdc82b9e4902ffdd1ade91add5e"},
|
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
argon2-cffi = {version = ">=23.1.0,<24", optional = true, markers = "extra == \"argon2\""}
|
|
||||||
bcrypt = {version = ">=4.1.2,<5", optional = true, markers = "extra == \"bcrypt\""}
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
argon2 = ["argon2-cffi (>=23.1.0,<24)"]
|
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||||
bcrypt = ["bcrypt (>=4.1.2,<5)"]
|
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||||
|
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||||
[[package]]
|
totp = ["cryptography"]
|
||||||
name = "pycparser"
|
|
||||||
version = "2.22"
|
|
||||||
description = "C parser in Python"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
|
||||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.11.5"
|
version = "2.11.7"
|
||||||
description = "Data validation using Python type hints"
|
description = "Data validation using Python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"},
|
{file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
|
||||||
{file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"},
|
{file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -867,9 +569,6 @@ files = [
|
|||||||
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
|
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
crypto = ["cryptography (>=3.4.0)"]
|
crypto = ["cryptography (>=3.4.0)"]
|
||||||
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
|
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||||
@@ -1011,7 +710,7 @@ files = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
greenlet = {version = ">=1", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""}
|
greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
|
||||||
typing-extensions = ">=4.6.0"
|
typing-extensions = ">=4.6.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -1106,4 +805,4 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.12"
|
python-versions = ">=3.12"
|
||||||
content-hash = "40669246978dc70b09cd8fef22d81e8140d3474feeba1023d8178124914ebcf8"
|
content-hash = "7f9ca5ce7505707747e59087ccbb804dc0fef135c963fd2d2ebc8c91285ec188"
|
||||||
|
|||||||
@@ -9,18 +9,85 @@ license = {text = "MIT"}
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi (>=0.115.12,<0.116.0)",
|
|
||||||
"sqlalchemy (>=2.0.41,<3.0.0)",
|
"sqlalchemy (>=2.0.41,<3.0.0)",
|
||||||
"fastapi-users[sqlalchemy] (>=14.0.1,<15.0.0)",
|
|
||||||
"uvicorn (>=0.34.2,<0.35.0)",
|
"uvicorn (>=0.34.2,<0.35.0)",
|
||||||
"aiosqlite (>=0.21.0,<0.22.0)",
|
"aiosqlite (>=0.21.0,<0.22.0)",
|
||||||
"pydantic-settings (>=2.9.1,<3.0.0)",
|
"pydantic-settings (>=2.9.1,<3.0.0)",
|
||||||
"alembic (>=1.16.1,<2.0.0)",
|
"alembic (>=1.16.1,<2.0.0)",
|
||||||
"ruff (>=0.11.12,<0.12.0)",
|
"ruff (>=0.11.12,<0.12.0)",
|
||||||
"greenlet (>=3.2.2,<4.0.0)"
|
"greenlet (>=3.2.2,<4.0.0)",
|
||||||
|
"pydantic (>=2.11.7,<3.0.0)",
|
||||||
|
"idna (>=3.10,<4.0)",
|
||||||
|
"fastapi (>=0.115.14,<0.116.0)",
|
||||||
|
"pyjwt (>=2.10.1,<3.0.0)",
|
||||||
|
"passlib (>=1.7.4,<2.0.0)",
|
||||||
|
"email-validator (>=2.2.0,<3.0.0)",
|
||||||
|
"bcrypt (==4.0.1)",
|
||||||
|
"python-multipart (>=0.0.20,<0.0.21)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
# Exclude commonly ignored directories and files.
|
||||||
|
exclude = [
|
||||||
|
".bzr",
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".git-rewrite",
|
||||||
|
".hg",
|
||||||
|
".ipynb_checkpoints",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pyenv",
|
||||||
|
".pytest_cache",
|
||||||
|
".pytype",
|
||||||
|
".ruff_cache",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".vscode",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"site-packages",
|
||||||
|
"venv",
|
||||||
|
]
|
||||||
|
# Set the maximum line length for both linting and formatting.
|
||||||
|
line-length = 88
|
||||||
|
# Assume Python 3.9 for compatibility checks.
|
||||||
|
target-version = "py39"
|
||||||
|
# Enable preview features for early access to new rules and formatting changes.
|
||||||
|
preview = true
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Select specific rule groups to enable.
|
||||||
|
# 'E' for pycodestyle, 'F' for Pyflakes, 'I' for isort, 'B' for flake8-bugbear.
|
||||||
|
select = [
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"I",
|
||||||
|
"B",
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
]
|
||||||
|
# Ignore specific rules within the selected groups.
|
||||||
|
ignore = [
|
||||||
|
"UP035",
|
||||||
|
"B903",
|
||||||
|
"B904",
|
||||||
|
"E501",
|
||||||
|
"B008",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
# Enable Ruff's formatter.
|
||||||
|
docstring-code-format = true
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
@@ -1,8 +1,8 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from src.api.users import router as users_router
|
|
||||||
from src.api.tasks import router as tasks_router
|
|
||||||
|
|
||||||
router = APIRouter()
|
from src.api.v1 import router as v1_router
|
||||||
|
from src.core.settings import settings
|
||||||
|
|
||||||
router.include_router(router=users_router)
|
router = APIRouter(prefix=settings.api.prefix)
|
||||||
router.include_router(router=tasks_router)
|
|
||||||
|
router.include_router(router=v1_router)
|
||||||
|
|||||||
15
src/api/dependacies/db_dep.py
Normal file
15
src/api/dependacies/db_dep.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from typing import Annotated, AsyncGenerator
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from src.core.database import async_session_maker
|
||||||
|
from src.core.db_manager import DBManager
|
||||||
|
|
||||||
|
|
||||||
|
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||||
|
async with DBManager(async_session_maker) as db:
|
||||||
|
yield db
|
||||||
|
|
||||||
|
|
||||||
|
sessionDep = Annotated[AsyncSession, Depends(get_db)]
|
||||||
64
src/api/dependacies/user_dep.py
Normal file
64
src/api/dependacies/user_dep.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, HTTPException, Path
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
from jwt import InvalidTokenError
|
||||||
|
|
||||||
|
from src.api.dependacies.db_dep import sessionDep
|
||||||
|
from src.core.auth_manager import AuthManager
|
||||||
|
from src.core.settings import settings
|
||||||
|
from src.schemas.auth import TokenData
|
||||||
|
from src.services.users import UserService
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.api.v1_login_url}/login")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
payload = AuthManager.decode_access_token(token=token)
|
||||||
|
if payload is None:
|
||||||
|
raise credentials_exception
|
||||||
|
user = TokenData(**payload)
|
||||||
|
except InvalidTokenError:
|
||||||
|
raise credentials_exception
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
CurrentUser = Annotated[TokenData, Depends(get_current_user)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_active_user(
|
||||||
|
current_user: CurrentUser,
|
||||||
|
):
|
||||||
|
if not current_user.is_active:
|
||||||
|
raise HTTPException(status_code=400, detail="Inactive user")
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
ActiveUser = Annotated[TokenData, Depends(get_current_active_user)]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_admin_user(db: sessionDep, current_user: ActiveUser):
|
||||||
|
await UserService(db).validate_admin_user(current_user.sub)
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
AdminUser = Annotated[TokenData, Depends(get_admin_user)]
|
||||||
|
|
||||||
|
|
||||||
|
async def user_or_admin(
|
||||||
|
db: sessionDep, current_user: ActiveUser, id: Annotated[int, Path()]
|
||||||
|
):
|
||||||
|
if current_user.id == id:
|
||||||
|
return current_user
|
||||||
|
else:
|
||||||
|
admin = await get_admin_user(db, current_user)
|
||||||
|
return admin
|
||||||
|
|
||||||
|
|
||||||
|
CurrentOrAdmin = Annotated[TokenData, Depends(user_or_admin)]
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from typing import Annotated, AsyncGenerator
|
|
||||||
|
|
||||||
from fastapi import Depends
|
|
||||||
from fastapi_users_db_sqlalchemy import SQLAlchemyUserDatabase
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.db.database import async_session_maker
|
|
||||||
from src.models import UsersORM
|
|
||||||
|
|
||||||
|
|
||||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
||||||
async with async_session_maker as db:
|
|
||||||
yield db
|
|
||||||
|
|
||||||
|
|
||||||
DBDep = Annotated[AsyncSession, Depends(get_db)]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_user_db(session: DBDep):
|
|
||||||
yield SQLAlchemyUserDatabase(session, UsersORM)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/users", tags=["Users"])
|
|
||||||
12
src/api/v1/__init__.py
Normal file
12
src/api/v1/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from src.api.v1.auth import router as auth_router
|
||||||
|
from src.api.v1.tasks import router as tasks_router
|
||||||
|
from src.api.v1.users import router as users_router
|
||||||
|
from src.core.settings import settings
|
||||||
|
|
||||||
|
router = APIRouter(prefix=settings.api.v1.prefix)
|
||||||
|
|
||||||
|
router.include_router(router=auth_router)
|
||||||
|
router.include_router(router=users_router)
|
||||||
|
router.include_router(router=tasks_router)
|
||||||
28
src/api/v1/auth.py
Normal file
28
src/api/v1/auth.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
|
from src.api.dependacies.db_dep import sessionDep
|
||||||
|
from src.core.settings import settings
|
||||||
|
from src.schemas.users import UserRequestADD
|
||||||
|
from src.services.auth import AuthService
|
||||||
|
|
||||||
|
router = APIRouter(prefix=settings.api.v1.auth, tags=["Auth"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(path="/signup")
|
||||||
|
async def registration(session: sessionDep, credential: UserRequestADD):
|
||||||
|
auth = await AuthService(session).registration(credential)
|
||||||
|
return auth
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(path="/login")
|
||||||
|
async def login(
|
||||||
|
session: sessionDep,
|
||||||
|
credential: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||||
|
):
|
||||||
|
access_token = await AuthService(session).login(
|
||||||
|
credential.username, credential.password
|
||||||
|
)
|
||||||
|
return access_token
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from src.api.dependacies.db_dep import sessionDep
|
||||||
|
from src.api.dependacies.user_dep import ActiveUser
|
||||||
|
from src.models.tasks import TasksORM
|
||||||
|
|
||||||
router = APIRouter(prefix="/tasks", tags=["Tasks"])
|
router = APIRouter(prefix="/tasks", tags=["Tasks"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def get_tasks(): ...
|
async def get_tasks(db: sessionDep, user: ActiveUser):
|
||||||
|
query = select(TasksORM.id, TasksORM.description).where(TasksORM.user_id == user.id)
|
||||||
|
tasks = await db.session.execute(query)
|
||||||
|
result = tasks.scalars().all()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{task_id}")
|
@router.get("/{task_id}")
|
||||||
40
src/api/v1/users.py
Normal file
40
src/api/v1/users.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from fastapi import APIRouter, Body
|
||||||
|
|
||||||
|
from src.api.dependacies.db_dep import sessionDep
|
||||||
|
from src.api.dependacies.user_dep import ActiveUser, AdminUser, CurrentOrAdmin
|
||||||
|
from src.core.settings import settings
|
||||||
|
from src.schemas.users import UserUpdate
|
||||||
|
from src.services.users import UserService
|
||||||
|
|
||||||
|
router = APIRouter(prefix=settings.api.v1.users, tags=["Users"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me")
|
||||||
|
async def get_me(user: ActiveUser):
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def get_all_users(db: sessionDep, _: AdminUser):
|
||||||
|
users = await UserService(db).get_all_users()
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{id}")
|
||||||
|
async def get_user_by_id(db: sessionDep, id: int, _: CurrentOrAdmin):
|
||||||
|
user = await UserService(db).get_user_by_filter_or_raise(id=id)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{id}")
|
||||||
|
async def patch_user(
|
||||||
|
db: sessionDep, id: int, _: CurrentOrAdmin, user_update: UserUpdate = Body()
|
||||||
|
):
|
||||||
|
updated_user = await UserService(db).update_user(id=id, update_data=user_update)
|
||||||
|
return updated_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{id}")
|
||||||
|
async def delete_user(db: sessionDep, id: int, _: AdminUser):
|
||||||
|
await UserService(db).delete_user(id)
|
||||||
|
return {"message": "User deleted successfully"}
|
||||||
0
src/core/__init__.py
Normal file
0
src/core/__init__.py
Normal file
43
src/core/auth_manager.py
Normal file
43
src/core/auth_manager.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
from src.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
class AuthManager:
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify_password(cls, plain_password, hashed_password):
|
||||||
|
return cls.pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_password_hash(cls, password):
|
||||||
|
return cls.pwd_context.hash(password)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_access_token(cls, data: dict, expires_delta: timedelta | None = None):
|
||||||
|
to_encode = data.copy()
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.now(timezone.utc) + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.now(timezone.utc) + timedelta(
|
||||||
|
minutes=settings.access_token.expire_minutes
|
||||||
|
)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(
|
||||||
|
to_encode,
|
||||||
|
settings.access_token.secret_key,
|
||||||
|
algorithm=settings.access_token.algorithm,
|
||||||
|
)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decode_access_token(cls, token: str) -> dict:
|
||||||
|
return jwt.decode(
|
||||||
|
token,
|
||||||
|
settings.access_token.secret_key,
|
||||||
|
algorithms=[settings.access_token.algorithm],
|
||||||
|
)
|
||||||
26
src/core/database.py
Normal file
26
src/core/database.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import TIMESTAMP, event, func
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
from src.core.settings import settings
|
||||||
|
|
||||||
|
engine = create_async_engine(settings.db.url, echo=True)
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(engine.sync_engine, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
if "sqlite" in settings.db.url:
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA foreign_keys=ON")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
async_session_maker = async_sessionmaker(bind=engine, expire_on_commit=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
TIMESTAMP(timezone=True), server_default=func.now()
|
||||||
|
)
|
||||||
20
src/core/db_manager.py
Normal file
20
src/core/db_manager.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from src.repository.tasks import TasksRepo
|
||||||
|
from src.repository.users import UsersRepo
|
||||||
|
|
||||||
|
|
||||||
|
class DBManager:
|
||||||
|
def __init__(self, session_factory):
|
||||||
|
self.session_factory = session_factory
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.session = self.session_factory()
|
||||||
|
self.user = UsersRepo(self.session)
|
||||||
|
self.task = TasksRepo(self.session)
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
await self.session.rollback()
|
||||||
|
await self.session.close()
|
||||||
|
|
||||||
|
async def commit(self):
|
||||||
|
await self.session.commit()
|
||||||
15
src/core/interfaces.py
Normal file
15
src/core/interfaces.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from src.repository.tasks import TasksRepo
|
||||||
|
from src.repository.users import UsersRepo
|
||||||
|
|
||||||
|
|
||||||
|
class IUOWDB(Protocol):
|
||||||
|
user: UsersRepo
|
||||||
|
task: TasksRepo
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "IUOWDB": ...
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ...
|
||||||
|
|
||||||
|
async def commit(self) -> None: ...
|
||||||
49
src/core/settings.py
Normal file
49
src/core/settings.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
|
DB_PATH = BASE_DIR / "db/taskncoffee.db"
|
||||||
|
|
||||||
|
|
||||||
|
class ApiV1Prefix(BaseModel):
|
||||||
|
prefix: str = "/v1"
|
||||||
|
auth: str = "/auth"
|
||||||
|
users: str = "/users"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def login_url(self) -> str:
|
||||||
|
return f"{self.prefix}{self.auth}"
|
||||||
|
|
||||||
|
|
||||||
|
class ApiPrefix(BaseModel):
|
||||||
|
prefix: str = "/api"
|
||||||
|
v1: ApiV1Prefix = ApiV1Prefix()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def v1_login_url(self) -> str:
|
||||||
|
return f"{self.prefix}{self.v1.login_url}"
|
||||||
|
|
||||||
|
|
||||||
|
class DbSettings(BaseModel):
|
||||||
|
url: str = f"sqlite+aiosqlite:///{DB_PATH}"
|
||||||
|
|
||||||
|
|
||||||
|
class AccessToken(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env", env_file_encoding="utf-8", env_prefix="ACCESS_TOKEN_"
|
||||||
|
)
|
||||||
|
expire_minutes: int = 15
|
||||||
|
secret_key: str
|
||||||
|
algorithm: str = "HS256"
|
||||||
|
token_type: str = "bearer" # noqa: S105
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
api: ApiPrefix = ApiPrefix()
|
||||||
|
db: DbSettings = DbSettings()
|
||||||
|
access_token: AccessToken = AccessToken()
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import TIMESTAMP, func
|
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
||||||
|
|
||||||
engine = create_async_engine("sqlite+aiosqlite:///src/db/taskncoffee.db", echo=True)
|
|
||||||
|
|
||||||
async_session_maker = async_sessionmaker(bind=engine, expire_on_commit=False)
|
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
|
||||||
created_at: Mapped[datetime] = mapped_column(
|
|
||||||
TIMESTAMP(timezone=True), server_default=func.now()
|
|
||||||
)
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
sys.path.append(str(Path(__file__).parent.parent))
|
||||||
from src.api import router
|
from src.api import router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI(title="Task&Coffee")
|
||||||
app.include_router(router=router)
|
app.include_router(router=router)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run("src.main:app", port=5000, log_level="info", reload=True)
|
uvicorn.run("src.main:app", port=8000, log_level="info", reload=True)
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
|
||||||
from sqlalchemy import pool
|
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
from sqlalchemy import engine_from_config, event, pool
|
||||||
|
|
||||||
from src.db.database import Base
|
from src.core.database import Base
|
||||||
from src.models import * # noqa
|
from src.models import * # noqa
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
@@ -67,10 +65,16 @@ def run_migrations_online() -> None:
|
|||||||
poolclass=pool.NullPool,
|
poolclass=pool.NullPool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Enable foreign keys for SQLite in migrations
|
||||||
|
@event.listens_for(connectable, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
print("⚙️ Enabling PRAGMA foreign_keys=ON for Alembic")
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA foreign_keys=ON")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
connection=connection, target_metadata=target_metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
"""init
|
|
||||||
|
|
||||||
Revision ID: 932121e6b220
|
|
||||||
Revises:
|
|
||||||
Create Date: 2025-06-22 11:52:49.691545
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
revision: str = '932121e6b220'
|
|
||||||
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:
|
|
||||||
"""Upgrade schema."""
|
|
||||||
op.create_table('users',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('username', sa.String(length=30), nullable=False),
|
|
||||||
sa.Column('telegram_id', sa.BigInteger(), nullable=True),
|
|
||||||
sa.Column('avatar_path', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('email', sa.String(length=320), nullable=False),
|
|
||||||
sa.Column('hashed_password', sa.String(length=1024), nullable=False),
|
|
||||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('is_superuser', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('is_verified', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
|
||||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
|
||||||
op.create_table('tasks',
|
|
||||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('title', sa.String(length=100), nullable=False),
|
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
|
||||||
sa.Column('due_date', sa.Date(), nullable=True),
|
|
||||||
sa.Column('status', sa.Enum('open', 'closed', 'in_progress', 'todo', name='status_enum'), nullable=False),
|
|
||||||
sa.CheckConstraint("status IN ('open', 'closed', 'in_progress', 'todo')", name="ck_status_enum"),
|
|
||||||
sa.Column('priority', sa.Enum('low', 'medium', 'high', 'critical', name='priority_enum'), nullable=False),
|
|
||||||
sa.CheckConstraint("priority in ('low', 'medium', 'high', 'critical')", name='ck_priority_enum'),
|
|
||||||
sa.Column('time_spent', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Downgrade schema."""
|
|
||||||
op.drop_table('tasks')
|
|
||||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
|
||||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
|
||||||
op.drop_table('users')
|
|
||||||
89
src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py
Normal file
89
src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
"""init
|
||||||
|
|
||||||
|
Revision ID: a2fdd0ec4a96
|
||||||
|
Revises:
|
||||||
|
Create Date: 2025-07-06 00:02:09.254907
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "a2fdd0ec4a96"
|
||||||
|
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:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"users",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("username", sa.String(length=30), nullable=False),
|
||||||
|
sa.Column("hashed_password", sa.String(length=255), nullable=False),
|
||||||
|
sa.Column("email", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("telegram_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("avatar_path", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("is_superuser", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column(
|
||||||
|
"created_at",
|
||||||
|
sa.TIMESTAMP(timezone=True),
|
||||||
|
server_default=sa.text("(CURRENT_TIMESTAMP)"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("email"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
|
||||||
|
op.create_table(
|
||||||
|
"tasks",
|
||||||
|
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("title", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("description", sa.Text(), nullable=True),
|
||||||
|
sa.Column("due_date", sa.Date(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"status",
|
||||||
|
sa.Enum("open", "closed", "in_progress", "todo", name="status_enum"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.CheckConstraint(
|
||||||
|
"status IN ('open', 'closed', 'in_progress', 'todo')", name="ck_status_enum"
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"priority",
|
||||||
|
sa.Enum("low", "medium", "high", "critical", name="priority_enum"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.CheckConstraint(
|
||||||
|
"priority in ('low', 'medium', 'high', 'critical')", name="ck_priority_enum"
|
||||||
|
),
|
||||||
|
sa.Column("time_spent", sa.Integer(), nullable=False),
|
||||||
|
sa.Column(
|
||||||
|
"created_at",
|
||||||
|
sa.TIMESTAMP(timezone=True),
|
||||||
|
server_default=sa.text("(CURRENT_TIMESTAMP)"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["users.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("tasks")
|
||||||
|
op.drop_index(op.f("ix_users_username"), table_name="users")
|
||||||
|
op.drop_table("users")
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""add_cascade_delete_to_tasks
|
||||||
|
|
||||||
|
Revision ID: 197b195208e8
|
||||||
|
Revises: a2fdd0ec4a96
|
||||||
|
Create Date: 2025-08-06 23:41:56.778423
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "197b195208e8"
|
||||||
|
down_revision: Union[str, None] = "a2fdd0ec4a96"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"""Upgrade schema."""
|
||||||
|
op.execute("PRAGMA foreign_keys=ON")
|
||||||
|
|
||||||
|
with op.batch_alter_table("tasks", schema=None) as batch_op:
|
||||||
|
connection = op.get_bind()
|
||||||
|
inspector = sa.inspect(connection)
|
||||||
|
|
||||||
|
foreign_keys = inspector.get_foreign_keys("tasks")
|
||||||
|
constraint_name = None
|
||||||
|
|
||||||
|
for fk in foreign_keys:
|
||||||
|
if "user_id" in fk["constrained_columns"]:
|
||||||
|
constraint_name = fk["name"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if constraint_name:
|
||||||
|
try: # noqa: SIM105
|
||||||
|
batch_op.drop_constraint(constraint_name, type_="foreignkey")
|
||||||
|
except: # noqa E722
|
||||||
|
pass
|
||||||
|
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"fk_tasks_user_id_users", "users", ["user_id"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"""Downgrade schema."""
|
||||||
|
with op.batch_alter_table("tasks", schema=None) as batch_op:
|
||||||
|
try: # noqa: SIM105
|
||||||
|
batch_op.drop_constraint("fk_tasks_user_id_users", type_="foreignkey")
|
||||||
|
except: # noqa E722
|
||||||
|
pass
|
||||||
|
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"fk_tasks_user_id_users", "users", ["user_id"], ["id"]
|
||||||
|
)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""fix_duplicate_foreign_keys
|
||||||
|
|
||||||
|
Revision ID: 4b0f3ea2fd26
|
||||||
|
Revises: 197b195208e8
|
||||||
|
Create Date: 2025-08-06 23:54:24.308488
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union # noqa: UP035
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "4b0f3ea2fd26"
|
||||||
|
down_revision: Union[str, None] = "197b195208e8"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"""Upgrade schema."""
|
||||||
|
op.execute("PRAGMA foreign_keys=ON")
|
||||||
|
|
||||||
|
with op.batch_alter_table("tasks", schema=None) as batch_op:
|
||||||
|
connection = op.get_bind()
|
||||||
|
inspector = sa.inspect(connection)
|
||||||
|
|
||||||
|
foreign_keys = inspector.get_foreign_keys("tasks")
|
||||||
|
|
||||||
|
for fk in foreign_keys:
|
||||||
|
if "user_id" in fk["constrained_columns"]:
|
||||||
|
try: # noqa: SIM105
|
||||||
|
batch_op.drop_constraint(fk["name"], type_="foreignkey")
|
||||||
|
except: # noqa E722
|
||||||
|
pass
|
||||||
|
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"fk_tasks_user_id_users", "users", ["user_id"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"""Downgrade schema."""
|
||||||
|
with op.batch_alter_table("tasks", schema=None) as batch_op:
|
||||||
|
try: # noqa: SIM105
|
||||||
|
batch_op.drop_constraint("fk_tasks_user_id_users", type_="foreignkey")
|
||||||
|
except: # noqa E722
|
||||||
|
pass
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"fk_tasks_user_id_users", "users", ["user_id"], ["id"]
|
||||||
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from src.models.users import UsersORM
|
|
||||||
from src.models.tasks import TasksORM
|
from src.models.tasks import TasksORM
|
||||||
|
from src.models.users import UsersORM
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"UsersORM",
|
"UsersORM",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey, Text, Date, Enum, String
|
from sqlalchemy import Date, Enum, ForeignKey, String, Text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from src.db.database import Base
|
from src.core.database import Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from src.models.users import UsersORM
|
from src.models.users import UsersORM
|
||||||
@@ -14,10 +14,9 @@ priority_enum = Enum("low", "medium", "high", "critical", name="priority_enum")
|
|||||||
|
|
||||||
|
|
||||||
class TasksORM(Base):
|
class TasksORM(Base):
|
||||||
|
|
||||||
__tablename__ = "tasks"
|
__tablename__ = "tasks"
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
|
||||||
title: Mapped[str] = mapped_column(String(100))
|
title: Mapped[str] = mapped_column(String(100))
|
||||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
due_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
due_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||||
@@ -25,4 +24,4 @@ class TasksORM(Base):
|
|||||||
priority: Mapped[str] = mapped_column(priority_enum, default="medium")
|
priority: Mapped[str] = mapped_column(priority_enum, default="medium")
|
||||||
time_spent: Mapped[int] = mapped_column(default=0)
|
time_spent: Mapped[int] = mapped_column(default=0)
|
||||||
|
|
||||||
user: Mapped["Users"] = relationship(back_populates="tasks")
|
user: Mapped["UsersORM"] = relationship(back_populates="tasks")
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
from typing import Optional, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable
|
from sqlalchemy import BigInteger, Boolean, Integer, String
|
||||||
from sqlalchemy import String, BigInteger, Integer
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from src.db.database import Base
|
from src.core.database import Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from src.models.tasks import TasksORM
|
from src.models.tasks import TasksORM
|
||||||
|
|
||||||
|
|
||||||
class UsersORM(SQLAlchemyBaseUserTable[int], Base):
|
class UsersORM(Base):
|
||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
username: Mapped[Optional[str]] = mapped_column(
|
username: Mapped[str] = mapped_column(
|
||||||
String(30), nullable=False, unique=True, index=True
|
String(30), nullable=False, unique=True, index=True
|
||||||
)
|
)
|
||||||
|
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
|
email: Mapped[Optional[str]] = mapped_column(
|
||||||
|
String(255), unique=True, nullable=True
|
||||||
|
)
|
||||||
telegram_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
telegram_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
||||||
avatar_path: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
avatar_path: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||||
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||||
tasks: Mapped[list["TasksORM"]] = relationship(back_populates="user")
|
is_superuser: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
|
tasks: Mapped[list["TasksORM"]] = relationship(
|
||||||
|
back_populates="user", cascade="all, delete-orphan"
|
||||||
|
)
|
||||||
|
|||||||
0
src/repository/__init__.py
Normal file
0
src/repository/__init__.py
Normal file
23
src/repository/base.py
Normal file
23
src/repository/base.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlalchemy import insert, select
|
||||||
|
|
||||||
|
from src.core.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRepo:
|
||||||
|
model: type[Base] = None
|
||||||
|
|
||||||
|
def __init__(self, session):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
async def create_one(self, data: BaseModel):
|
||||||
|
statement = insert(self.model).values(data.model_dump()).returning(self.model)
|
||||||
|
result = await self.session.execute(statement)
|
||||||
|
obj = result.scalar_one()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
async def get_one_or_none(self, **filter_by):
|
||||||
|
query = select(self.model).filter_by(**filter_by)
|
||||||
|
result = await self.session.execute(query)
|
||||||
|
model = result.scalars().one_or_none()
|
||||||
|
return model
|
||||||
6
src/repository/tasks.py
Normal file
6
src/repository/tasks.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from src.models.tasks import TasksORM
|
||||||
|
from src.repository.base import BaseRepo
|
||||||
|
|
||||||
|
|
||||||
|
class TasksRepo(BaseRepo):
|
||||||
|
model = TasksORM
|
||||||
28
src/repository/users.py
Normal file
28
src/repository/users.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from sqlalchemy import delete, select, update
|
||||||
|
|
||||||
|
from src.models import UsersORM
|
||||||
|
from src.repository.base import BaseRepo
|
||||||
|
|
||||||
|
|
||||||
|
class UsersRepo(BaseRepo):
|
||||||
|
model = UsersORM
|
||||||
|
|
||||||
|
async def get_all_users(self) -> list[UsersORM]:
|
||||||
|
query = select(self.model)
|
||||||
|
result = await self.session.execute(query)
|
||||||
|
models = result.scalars().all()
|
||||||
|
return models
|
||||||
|
|
||||||
|
async def delete_one(self, id: int) -> None:
|
||||||
|
await self.session.execute(delete(self.model).where(self.model.id == id))
|
||||||
|
|
||||||
|
async def update_one(self, id: int, data: dict) -> UsersORM:
|
||||||
|
stmt = (
|
||||||
|
update(self.model)
|
||||||
|
.where(self.model.id == id)
|
||||||
|
.values(data.model_dump(exclude_unset=True))
|
||||||
|
.returning(self.model)
|
||||||
|
)
|
||||||
|
result = await self.session.execute(stmt)
|
||||||
|
model = result.scalar_one()
|
||||||
|
return model
|
||||||
12
src/schemas/auth.py
Normal file
12
src/schemas/auth.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Token(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class TokenData(BaseModel):
|
||||||
|
id: int | None = None
|
||||||
|
sub: str | None = None
|
||||||
|
is_active: bool
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from pydantic import BaseModel, BeforeValidator, ConfigDict, EmailStr
|
||||||
|
|
||||||
|
from src.schemas.validators import ensure_password
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
email: EmailStr | None = None
|
||||||
|
username: str | None = None
|
||||||
|
is_active: bool | None = None
|
||||||
|
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
id: int
|
||||||
|
email: EmailStr | None
|
||||||
|
username: str
|
||||||
|
is_active: bool
|
||||||
|
is_superuser: bool
|
||||||
|
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
class UserWithHashedPass(User):
|
||||||
|
hashed_password: str
|
||||||
|
|
||||||
|
|
||||||
|
class UserRequest(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class UserRequestADD(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: EmailStr | None = None
|
||||||
|
password: Annotated[str, BeforeValidator(ensure_password)]
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdd(BaseModel):
|
||||||
|
email: EmailStr | None
|
||||||
|
username: str
|
||||||
|
hashed_password: str
|
||||||
|
|||||||
11
src/schemas/validators.py
Normal file
11
src/schemas/validators.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_password(value: Any) -> Any:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise TypeError("Password must be a string")
|
||||||
|
if len(value) < 8:
|
||||||
|
raise ValueError("Password must be at least 8 characters")
|
||||||
|
if value.strip() == "":
|
||||||
|
raise ValueError("Password cannot be empty")
|
||||||
|
return value
|
||||||
0
src/services/__init__.py
Normal file
0
src/services/__init__.py
Normal file
43
src/services/auth.py
Normal file
43
src/services/auth.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from src.core.auth_manager import AuthManager
|
||||||
|
from src.core.settings import settings
|
||||||
|
from src.schemas.auth import Token
|
||||||
|
from src.schemas.users import User, UserAdd, UserRequestADD, UserWithHashedPass
|
||||||
|
from src.services.base import BaseService
|
||||||
|
|
||||||
|
|
||||||
|
class AuthService(BaseService):
|
||||||
|
async def registration(self, cred: UserRequestADD) -> User:
|
||||||
|
hashed_pass = AuthManager.get_password_hash(cred.password)
|
||||||
|
user_to_insert = UserAdd(
|
||||||
|
username=cred.username,
|
||||||
|
email=cred.email,
|
||||||
|
hashed_password=hashed_pass,
|
||||||
|
)
|
||||||
|
result = await self.session.user.create_one(user_to_insert)
|
||||||
|
await self.session.commit()
|
||||||
|
return User.model_validate(result)
|
||||||
|
|
||||||
|
async def login(self, username: str, password: str):
|
||||||
|
result = await self.session.user.get_one_or_none(username=username)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Incorrect username or password",
|
||||||
|
)
|
||||||
|
user = UserWithHashedPass.model_validate(result)
|
||||||
|
verify = AuthManager.verify_password(
|
||||||
|
plain_password=password, hashed_password=user.hashed_password
|
||||||
|
)
|
||||||
|
if not verify or user.is_active is False:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Incorrect username or password",
|
||||||
|
)
|
||||||
|
access_token = AuthManager.create_access_token(
|
||||||
|
data={"id": user.id, "sub": user.username, "is_active": user.is_active}
|
||||||
|
)
|
||||||
|
return Token(
|
||||||
|
access_token=access_token, token_type=settings.access_token.token_type
|
||||||
|
)
|
||||||
8
src/services/base.py
Normal file
8
src/services/base.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from src.core.interfaces import IUOWDB
|
||||||
|
|
||||||
|
|
||||||
|
class BaseService:
|
||||||
|
session: IUOWDB | None
|
||||||
|
|
||||||
|
def __init__(self, session: "IUOWDB"):
|
||||||
|
self.session = session
|
||||||
4
src/services/tasks.py
Normal file
4
src/services/tasks.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from src.services.base import BaseService
|
||||||
|
|
||||||
|
|
||||||
|
class TasksService(BaseService): ...
|
||||||
38
src/services/users.py
Normal file
38
src/services/users.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from src.schemas.users import User, UserUpdate
|
||||||
|
from src.services.base import BaseService
|
||||||
|
|
||||||
|
|
||||||
|
class UserService(BaseService):
|
||||||
|
async def get_user_by_filter(self, **filter_by) -> User | None:
|
||||||
|
result = await self.session.user.get_one_or_none(**filter_by)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return User.model_validate(result)
|
||||||
|
|
||||||
|
async def get_user_by_filter_or_raise(self, **filter_by) -> User:
|
||||||
|
user = await self.get_user_by_filter(**filter_by)
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def validate_admin_user(self, username: str) -> User:
|
||||||
|
user = await self.get_user_by_filter_or_raise(username=username)
|
||||||
|
if not user.is_superuser:
|
||||||
|
raise HTTPException(status_code=403, detail="Admin access required")
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def get_all_users(self) -> list[User]:
|
||||||
|
users = await self.session.user.get_all_users()
|
||||||
|
return [User.model_validate(user) for user in users]
|
||||||
|
|
||||||
|
async def delete_user(self, id: int) -> None:
|
||||||
|
await self.session.user.delete_one(id=id)
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
async def update_user(self, id: int, update_data: UserUpdate) -> User:
|
||||||
|
await self.get_user_by_filter_or_raise(id=id)
|
||||||
|
user = await self.session.user.update_one(id=id, data=update_data)
|
||||||
|
await self.session.commit()
|
||||||
|
return User.model_validate(user)
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from pydantic_settings import BaseSettings
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
|
||||||
...
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user