From 8039c846f02aad537e45f7e65f9de182ee361ded Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Fri, 18 Jul 2025 13:42:49 +0200 Subject: [PATCH 01/28] update toml for add test libs --- pyproject.toml | 13 +++- uv.lock | 163 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9626c5b..305da9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,16 @@ version = "0.4.0" requires-python = ">=3.13" dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1",] +[project.optional-dependencies] +testing = [ + "pytest>=7.0", + "pytest-asyncio>=0.20", + "httpx>=0.24", + "pytest-cov>=4.0", + "coverage>=6.0" +] + + [build-system] requires = [ "setuptools>=42", "wheel",] build-backend = "setuptools.build_meta" - -[tool.setuptools.packages.find] -include = [ "app*", "alembic*",] diff --git a/uv.lock b/uv.lock index e689925..3127f70 100644 --- a/uv.lock +++ b/uv.lock @@ -102,6 +102,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/17/602915b29cb695e1e66f65e33b1026f1534e49975d99ea4e32e58d963542/botocore-1.37.37-py3-none-any.whl", hash = "sha256:eb730ff978f47c02f0c8ed07bccdc0db6d8fa098ed32ac31bee1da0e9be480d1", size = 13495584, upload_time = "2025-04-18T19:21:26.45Z" }, ] +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload_time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload_time = "2025-07-14T03:29:26.863Z" }, +] + [[package]] name = "cffi" version = "1.17.1" @@ -145,6 +154,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload_time = "2025-07-03T10:54:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload_time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload_time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload_time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload_time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload_time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload_time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload_time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload_time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload_time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload_time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload_time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload_time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload_time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload_time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload_time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload_time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload_time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload_time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload_time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload_time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload_time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload_time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload_time = "2025-07-03T10:54:13.491Z" }, +] + [[package]] name = "cryptography" version = "44.0.2" @@ -270,6 +310,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload_time = "2022-09-25T15:39:59.68Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385, upload_time = "2025-04-11T14:42:46.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732, upload_time = "2025-04-11T14:42:44.896Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload_time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload_time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -279,6 +347,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload_time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload_time = "2025-03-19T20:10:01.071Z" }, +] + [[package]] name = "jmespath" version = "1.0.1" @@ -288,6 +365,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload_time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload_time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload_time = "2025-04-19T11:48:57.875Z" }, +] + [[package]] name = "passlib" version = "1.7.4" @@ -302,6 +388,15 @@ bcrypt = [ { name = "bcrypt" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload_time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload_time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "psycopg2-binary" version = "2.9.10" @@ -382,6 +477,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload_time = "2025-04-02T09:48:17.97Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload_time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload_time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload_time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload_time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload_time = "2025-07-16T04:29:26.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload_time = "2025-07-16T04:29:24.929Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload_time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload_time = "2025-06-12T10:47:45.932Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -503,7 +649,7 @@ wheels = [ [[package]] name = "tracknatrainapi" -version = "0.1.0" +version = "0.4.0" source = { editable = "." } dependencies = [ { name = "annotated-types" }, @@ -545,6 +691,15 @@ dependencies = [ { name = "uvicorn" }, ] +[package.optional-dependencies] +testing = [ + { name = "coverage" }, + { name = "httpx" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + [package.metadata] requires-dist = [ { name = "annotated-types", specifier = "==0.7.0" }, @@ -554,6 +709,7 @@ requires-dist = [ { name = "botocore", specifier = "==1.37.37" }, { name = "cffi", specifier = "==1.17.1" }, { name = "click", specifier = "==8.1.8" }, + { name = "coverage", marker = "extra == 'testing'", specifier = ">=6.0" }, { name = "cryptography", specifier = "==44.0.2" }, { name = "dnspython", specifier = "==2.7.0" }, { name = "ecdsa", specifier = "==0.19.1" }, @@ -562,6 +718,7 @@ requires-dist = [ { name = "fastapi", specifier = "==0.115.12" }, { name = "greenlet", specifier = "==3.1.1" }, { name = "h11", specifier = "==0.14.0" }, + { name = "httpx", marker = "extra == 'testing'", specifier = ">=0.24" }, { name = "idna", specifier = "==3.10" }, { name = "jmespath", specifier = "==1.0.1" }, { name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" }, @@ -570,6 +727,9 @@ requires-dist = [ { name = "pycparser", specifier = "==2.22" }, { name = "pydantic", specifier = "==2.11.3" }, { name = "pydantic-core", specifier = "==2.33.1" }, + { name = "pytest", marker = "extra == 'testing'", specifier = ">=7.0" }, + { name = "pytest-asyncio", marker = "extra == 'testing'", specifier = ">=0.20" }, + { name = "pytest-cov", marker = "extra == 'testing'", specifier = ">=4.0" }, { name = "python-dateutil", specifier = "==2.9.0.post0" }, { name = "python-dotenv", specifier = "==1.1.0" }, { name = "python-jose", specifier = "==3.4.0" }, @@ -585,6 +745,7 @@ requires-dist = [ { name = "urllib3", specifier = "==2.4.0" }, { name = "uvicorn", specifier = "==0.34.1" }, ] +provides-extras = ["testing"] [[package]] name = "typing-extensions" From 64cba5d7c055404b4284bffe2aebce939a79bd3a Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Fri, 18 Jul 2025 13:43:00 +0200 Subject: [PATCH 02/28] create a admin in mock data --- src/container.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/container.py b/src/container.py index 44f73fc..9228f83 100644 --- a/src/container.py +++ b/src/container.py @@ -1,4 +1,5 @@ import os +from uuid import uuid4, UUID from src.domain.lib.security import BcryptPasswordHasher from src.domain.services.profile import ProfileService @@ -13,12 +14,29 @@ def __init__(self, env: str | None = None): self.hasher = BcryptPasswordHasher() if self.env in ("dev", "test"): + from src.domain.model.profile import Profile as DomainProfile + plain_pw = "123456789" + hashed_pw = self.hasher.hash(plain_pw) + admin = DomainProfile( + id=uuid4(), + email="admin@mail.fr", + password=hashed_pw, + name="Admin", + sex=None, + age=None, + contact=None, + pricing=None, + description=None, + legacy=False, + roles=["admin"], + created_at=None, + ) from src.adapters.inmemory.repositories.profile import InMemoryProfileRepository from src.adapters.inmemory.repositories.group import InMemoryGroupRepository from src.adapters.inmemory.repositories.training import InMemoryTrainingRepository from src.adapters.inmemory.repositories.exercise import InMemoryExerciseRepository from src.adapters.inmemory.repositories.diet import InMemoryDietRepository - self.profile_repo = InMemoryProfileRepository() + self.profile_repo = InMemoryProfileRepository(initial=[admin]) self.group_repo = InMemoryGroupRepository() self.training_repo = InMemoryTrainingRepository() self.exercise_repo = InMemoryExerciseRepository() From 4bbb4221d433ffdc2eaac51a33e4121b4caf7ca7 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Fri, 18 Jul 2025 13:43:14 +0200 Subject: [PATCH 03/28] init the admin data --- src/adapters/inmemory/repositories/profile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/adapters/inmemory/repositories/profile.py b/src/adapters/inmemory/repositories/profile.py index 45aa64c..5c02bf8 100644 --- a/src/adapters/inmemory/repositories/profile.py +++ b/src/adapters/inmemory/repositories/profile.py @@ -7,8 +7,11 @@ class InMemoryProfileRepository(ProfileRepository): - def __init__(self): + def __init__(self, initial: list[DomainProfile] | None = None): self._data: dict[UUID, DomainProfile] = {} + if initial: + for profile in initial: + self._data[profile.id] = profile def find_by_email(self, email: str) -> Optional[DomainProfile]: for profile in self._data.values(): From 0de341825c59f9a1d8ee6f16b87a82be450d523b Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Fri, 18 Jul 2025 13:43:30 +0200 Subject: [PATCH 04/28] create conf test and some test for profil still in progress --- src/entrypoints/api/tests/conftest.py | 61 +++++++ src/entrypoints/api/tests/profiles.py | 238 ++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 src/entrypoints/api/tests/conftest.py create mode 100644 src/entrypoints/api/tests/profiles.py diff --git a/src/entrypoints/api/tests/conftest.py b/src/entrypoints/api/tests/conftest.py new file mode 100644 index 0000000..c53ec59 --- /dev/null +++ b/src/entrypoints/api/tests/conftest.py @@ -0,0 +1,61 @@ +import pytest +import os +from uuid import uuid4 + +import pytest_asyncio +from httpx import AsyncClient, ASGITransport + +from src.main import app +from src.container import Container +from src.domain.model.profile import Profile as DomainProfile +from src.domain.model.group import Group as DomainGroup + +@pytest.fixture(autouse=True) +def seed_data(monkeypatch): + # 1) Forcer ENV=test et container unique + monkeypatch.setenv("ENV", "test") + c = Container(env="test") + monkeypatch.setattr("src.container.container", c) + + # helper pour créer et seed des profils + def _mk(email: str, pw: str, roles: list[str]): + h = c.hasher.hash(pw) + p = DomainProfile( + id=uuid4(), email=email, password=h, + name=email.split("@")[0], sex=None, age=None, + contact=None, pricing=None, description=None, + legacy=False, roles=roles, created_at=None + ) + c.profile_repo.add(p) + return p, pw + + admin = _mk("admin@example.com", "AdminPass123!", ["admin"]) + coach = _mk("coach@example.com", "CoachPass123!", ["coach"]) + user = _mk("user@example.com", "UserPass123!", ["user"]) + + # Seed un groupe pour le coach + grp = DomainGroup( + id=uuid4(), + owner_id=coach[0].id, + name="GroupeTest", + description="Pour tests", + created_at=None + ) + c.group_repo.add(grp) + + return { + "container": c, + "admin": admin, + "coach": coach, + "user": user, + "group": grp, + } + +@pytest_asyncio.fixture +async def client(): + """ + Fournit un AsyncClient HTTPX monté sur l'ASGI FastAPI. + """ + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://testserver") as ac: + yield ac diff --git a/src/entrypoints/api/tests/profiles.py b/src/entrypoints/api/tests/profiles.py new file mode 100644 index 0000000..b09e51f --- /dev/null +++ b/src/entrypoints/api/tests/profiles.py @@ -0,0 +1,238 @@ +# src/entrypoints/api/tests/test_profiles.py +import pytest +from uuid import uuid4 + +@pytest.mark.asyncio +class TestProfiles: + + async def test_create_profile_success(self, client): + payload = { + "email": "alice@example.com", + "password": "Secret123!", + "confirm_password": "Secret123!", + "name": "Alice", + "sex": "F", + "age": 28 + } + r = await client.post("/profiles", json=payload) + assert r.status_code == 201 + body = r.json() + assert "profile" in body and "token" in body + assert body["profile"]["email"] == "alice@example.com" + + async def test_create_duplicate_email_returns_400(self, client): + payload = { + "email": "bob@example.com", + "password": "Pwd123!", + "confirm_password": "Pwd123!", + "name": "Bob" + } + r1 = await client.post("/profiles", json=payload) + assert r1.status_code == 201 + r2 = await client.post("/profiles", json=payload) + assert r2.status_code == 400 + assert "duplicate" in r2.json()["detail"].lower() + + async def test_login_and_get_me(self, client): + data = { + "email": "charlie@example.com", + "password": "TopSecret!", + "confirm_password": "TopSecret!", + "name": "Charlie" + } + r = await client.post("/profiles", json=data) + token = r.json()["token"]["access_token"] + headers = {"Authorization": f"Bearer {token}"} + r2 = await client.get("/profiles/me", headers=headers) + assert r2.status_code == 200 + assert r2.json()["email"] == "charlie@example.com" + + async def test_login_bad_credentials_returns_401(self, client, seed_data): + admin, _ = seed_data["admin"] + r = await client.post( + "/profiles/login", + json={"email": admin.email, "password": "wrong!"} + ) + assert r.status_code == 401 + + async def test_get_all_users_requires_admin_and_returns_list(self, client, seed_data): + # sans token → 403 + r1 = await client.get("/profiles/users") + assert r1.status_code == 403 + + # avec token admin + admin, admin_pw = seed_data["admin"] + r_login = await client.post( + "/profiles/login", + json={"email": admin.email, "password": admin_pw} + ) + token = r_login.json()["access_token"] + r2 = await client.get( + "/profiles/users", + headers={"Authorization": f"Bearer {token}"} + ) + assert r2.status_code == 200 + users = r2.json() + assert any(u["email"] == admin.email for u in users) + + async def test_get_coach_profiles_empty_and_after_creation(self, client): + # d'abord, aucun coach + r1 = await client.get("/profiles/coachs") + assert r1.status_code == 200 + assert r1.json() == [] + + # on en crée un + payload = { + "email": "coach2@example.com", + "password": "Coach123!", + "confirm_password": "Coach123!", + "name": "Coach2" + } + r2 = await client.post("/profiles", json=payload) + assert r2.status_code == 201 + + # puis on vérifie + r3 = await client.get("/profiles/coachs") + emails = [c["email"] for c in r3.json()] + assert "coach2@example.com" in emails + + async def test_delete_profile_owner_or_admin(self, client, seed_data): + # crée Dave + r = await client.post( + "/profiles", + json={"email": "dave@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} + ) + pid = r.json()["profile"]["id"] + token_owner = r.json()["token"]["access_token"] + + # crée Eve + r_e = await client.post( + "/profiles", + json={"email": "eve@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} + ) + token_e = r_e.json()["token"]["access_token"] + + # forbidden pour Eve + r_f = await client.delete( + f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_e}"} + ) + assert r_f.status_code == 403 + + # suppression par le propriétaire + r_ok = await client.delete( + f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_owner}"} + ) + assert r_ok.status_code == 204 + + # tentative de suppressions ultérieure → 404 + r_nf = await client.delete( + f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_owner}"} + ) + assert r_nf.status_code == 404 + + async def test_patch_profile_and_404(self, client): + fake_id = str(uuid4()) + r_nf = await client.patch(f"/profiles/{fake_id}", json={"name": "New"}) + assert r_nf.status_code in (403, 404) + + # création puis patch réussi + r = await client.post( + "/profiles", + json={"email": "guy@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} + ) + pid = r.json()["profile"]["id"] + token = r.json()["token"]["access_token"] + + r_patch = await client.patch( + f"/profiles/{pid}", + json={"name": "Guy Updated"}, + headers={"Authorization": f"Bearer {token}"} + ) + assert r_patch.status_code == 200 + assert r_patch.json()["name"] == "Guy Updated" + + async def test_patch_email_duplicate_and_notfound(self, client): + # on crée deux profils + r1 = await client.post( + "/profiles", json={"email": "foo@example.com", "password": "A1!", "confirm_password": "A1!"} + ) + r2 = await client.post( + "/profiles", json={"email": "bar@example.com", "password": "B2!", "confirm_password": "B2!"} + ) + id1 = r1.json()["profile"]["id"] + token = r2.json()["token"]["access_token"] + + # on tente un duplicate email + r_dup = await client.patch( + f"/profiles/{id1}/email", + json={"email": "foo@example.com"}, + headers={"Authorization": f"Bearer {token}"} + ) + assert r_dup.status_code == 400 + + # on tente sur ID inexistant + fake = str(uuid4()) + r_nf = await client.patch( + f"/profiles/{fake}/email", + json={"email": "x@y.com"}, + headers={"Authorization": f"Bearer {token}"} + ) + assert r_nf.status_code == 404 + + async def test_patch_password(self, client): + # création + r = await client.post( + "/profiles", + json={"email": "harry@example.com", "password": "OldPass!", "confirm_password": "OldPass!"} + ) + pid = r.json()["profile"]["id"] + token = r.json()["token"]["access_token"] + + # mauvais ancien mot de passe + r_bad = await client.patch( + f"/profiles/{pid}/password", + json={"old_password": "Bad!", "new_password": "NewPass!"}, + headers={"Authorization": f"Bearer {token}"} + ) + assert r_bad.status_code == 400 + + # correct + r_ok = await client.patch( + f"/profiles/{pid}/password", + json={"old_password": "OldPass!", "new_password": "NewPass!"}, + headers={"Authorization": f"Bearer {token}"} + ) + assert r_ok.status_code == 204 + + async def test_patch_roles_protected_and_success(self, client, seed_data): + # création d'un user lambda + r = await client.post( + "/profiles", + json={"email": "ian@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} + ) + pid = r.json()["profile"]["id"] + token_user = r.json()["token"]["access_token"] + + # il ne peut pas se donner des rôles + r_403 = await client.patch( + f"/profiles/{pid}/roles", + json={"roles": ["admin"]}, + headers={"Authorization": f"Bearer {token_user}"} + ) + assert r_403.status_code == 403 + + # l'admin peut + admin, admin_pw = seed_data["admin"] + r_login = await client.post( + "/profiles/login", + json={"email": admin.email, "password": admin_pw} + ) + token_admin = r_login.json()["access_token"] + + r_admin = await client.patch( + f"/profiles/{pid}/roles", + json={"roles": ["coach"]}, + headers={"Authorization": f"Bearer {token_admin}"} + ) + assert r_admin.status_code == 200 + assert "coach" in r_admin.json()["roles"] From bea75f8420fd1e04efe9e521a98afd830f56a8bd Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:41:48 +0200 Subject: [PATCH 05/28] add test in dep cause fuck u --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 305da9a..8c61792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,11 @@ name = "tracknatrainapi" version = "0.4.0" requires-python = ">=3.13" -dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1",] +dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1", "pytest>=7.0", + "pytest-asyncio>=0.20", + "httpx>=0.24", + "pytest-cov>=4.0", + "coverage>=6.0" ] [project.optional-dependencies] testing = [ From a7c0354502fa957493505dac0c2a0e9140064a7a Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:41:57 +0200 Subject: [PATCH 06/28] update uv lock --- uv.lock | 502 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 256 insertions(+), 246 deletions(-) diff --git a/uv.lock b/uv.lock index 3127f70..4f8c15b 100644 --- a/uv.lock +++ b/uv.lock @@ -6,9 +6,9 @@ requires-python = ">=3.13" name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload_time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload_time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -19,59 +19,59 @@ dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload_time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload_time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] [[package]] name = "bcrypt" version = "4.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload_time = "2025-02-28T01:24:09.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload_time = "2025-02-28T01:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload_time = "2025-02-28T01:22:38.078Z" }, - { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload_time = "2025-02-28T01:22:40.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload_time = "2025-02-28T01:22:43.144Z" }, - { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload_time = "2025-02-28T01:22:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload_time = "2025-02-28T01:22:47.023Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload_time = "2025-02-28T01:22:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload_time = "2025-02-28T01:22:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload_time = "2025-02-28T01:22:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload_time = "2025-02-28T01:22:55.461Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload_time = "2025-02-28T01:22:57.81Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload_time = "2025-02-28T01:22:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload_time = "2025-02-28T01:23:00.763Z" }, - { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload_time = "2025-02-28T01:23:02.908Z" }, - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload_time = "2025-02-28T01:23:05.838Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload_time = "2025-02-28T01:23:07.274Z" }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload_time = "2025-02-28T01:23:09.151Z" }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload_time = "2025-02-28T01:23:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload_time = "2025-02-28T01:23:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload_time = "2025-02-28T01:23:14.5Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload_time = "2025-02-28T01:23:16.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload_time = "2025-02-28T01:23:18.897Z" }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload_time = "2025-02-28T01:23:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload_time = "2025-02-28T01:23:23.183Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload_time = "2025-02-28T01:23:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload_time = "2025-02-28T01:23:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload_time = "2025-02-28T01:23:28.381Z" }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload_time = "2025-02-28T01:23:30.187Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload_time = "2025-02-28T01:23:31.945Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload_time = "2025-02-28T01:23:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload_time = "2025-02-28T01:23:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload_time = "2025-02-28T01:23:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload_time = "2025-02-28T01:23:39.575Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload_time = "2025-02-28T01:23:40.901Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload_time = "2025-02-28T01:23:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload_time = "2025-02-28T01:23:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload_time = "2025-02-28T01:23:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload_time = "2025-02-28T01:23:47.575Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload_time = "2025-02-28T01:23:49.059Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload_time = "2025-02-28T01:23:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload_time = "2025-02-28T01:23:51.775Z" }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload_time = "2025-02-28T01:23:53.139Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, + { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, + { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, + { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, + { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, + { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, + { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, + { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, + { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, + { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, + { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, ] [[package]] @@ -83,9 +83,9 @@ dependencies = [ { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/8c/2ca661db6c9e591d9dc46149b43a91385283c852436ccba62e199643e196/boto3-1.37.37.tar.gz", hash = "sha256:752d31105a45e3e01c8c68471db14ae439990b75a35e72b591ca528e2575b28f", size = 111666, upload_time = "2025-04-18T19:21:46.741Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/8c/2ca661db6c9e591d9dc46149b43a91385283c852436ccba62e199643e196/boto3-1.37.37.tar.gz", hash = "sha256:752d31105a45e3e01c8c68471db14ae439990b75a35e72b591ca528e2575b28f", size = 111666, upload-time = "2025-04-18T19:21:46.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/5f/032d93e74949222ffbfbc3270f29a3ee423fe648de8a31c49cce0cbb0a09/boto3-1.37.37-py3-none-any.whl", hash = "sha256:d125cb11e22817f7a2581bade4bf7b75247b401888890239ceb5d3e902ccaf38", size = 139917, upload_time = "2025-04-18T19:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5f/032d93e74949222ffbfbc3270f29a3ee423fe648de8a31c49cce0cbb0a09/boto3-1.37.37-py3-none-any.whl", hash = "sha256:d125cb11e22817f7a2581bade4bf7b75247b401888890239ceb5d3e902ccaf38", size = 139917, upload-time = "2025-04-18T19:21:43.769Z" }, ] [[package]] @@ -97,18 +97,18 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d0/70969515e3ae8ff0fcccf22827d5d131bc7b8729331127415cf8f2861d63/botocore-1.37.37.tar.gz", hash = "sha256:3eadde6fed95c4cb469cc39d1c3558528b7fa76d23e7e16d4bddc77250431a64", size = 13828530, upload_time = "2025-04-18T19:21:32.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d0/70969515e3ae8ff0fcccf22827d5d131bc7b8729331127415cf8f2861d63/botocore-1.37.37.tar.gz", hash = "sha256:3eadde6fed95c4cb469cc39d1c3558528b7fa76d23e7e16d4bddc77250431a64", size = 13828530, upload-time = "2025-04-18T19:21:32.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/17/602915b29cb695e1e66f65e33b1026f1534e49975d99ea4e32e58d963542/botocore-1.37.37-py3-none-any.whl", hash = "sha256:eb730ff978f47c02f0c8ed07bccdc0db6d8fa098ed32ac31bee1da0e9be480d1", size = 13495584, upload_time = "2025-04-18T19:21:26.45Z" }, + { url = "https://files.pythonhosted.org/packages/fe/17/602915b29cb695e1e66f65e33b1026f1534e49975d99ea4e32e58d963542/botocore-1.37.37-py3-none-any.whl", hash = "sha256:eb730ff978f47c02f0c8ed07bccdc0db6d8fa098ed32ac31bee1da0e9be480d1", size = 13495584, upload-time = "2025-04-18T19:21:26.45Z" }, ] [[package]] name = "certifi" version = "2025.7.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload_time = "2025-07-14T03:29:28.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload_time = "2025-07-14T03:29:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] @@ -118,19 +118,19 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload_time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload_time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload_time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload_time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload_time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload_time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload_time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload_time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload_time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload_time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload_time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload_time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] [[package]] @@ -140,49 +140,49 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload_time = "2025-07-03T10:54:15.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload_time = "2025-07-03T10:53:25.811Z" }, - { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload_time = "2025-07-03T10:53:27.075Z" }, - { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload_time = "2025-07-03T10:53:28.408Z" }, - { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload_time = "2025-07-03T10:53:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload_time = "2025-07-03T10:53:31.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload_time = "2025-07-03T10:53:32.717Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload_time = "2025-07-03T10:53:34.009Z" }, - { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload_time = "2025-07-03T10:53:35.434Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload_time = "2025-07-03T10:53:36.787Z" }, - { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload_time = "2025-07-03T10:53:38.188Z" }, - { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload_time = "2025-07-03T10:53:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload_time = "2025-07-03T10:53:40.874Z" }, - { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload_time = "2025-07-03T10:53:42.218Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload_time = "2025-07-03T10:53:43.823Z" }, - { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload_time = "2025-07-03T10:53:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload_time = "2025-07-03T10:53:46.931Z" }, - { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload_time = "2025-07-03T10:53:48.289Z" }, - { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload_time = "2025-07-03T10:53:49.99Z" }, - { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload_time = "2025-07-03T10:53:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload_time = "2025-07-03T10:53:52.808Z" }, - { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload_time = "2025-07-03T10:53:54.273Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload_time = "2025-07-03T10:53:56.715Z" }, - { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload_time = "2025-07-03T10:54:13.491Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, ] [[package]] @@ -192,41 +192,41 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807, upload_time = "2025-03-02T00:01:37.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361, upload_time = "2025-03-02T00:00:06.528Z" }, - { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350, upload_time = "2025-03-02T00:00:09.537Z" }, - { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572, upload_time = "2025-03-02T00:00:12.03Z" }, - { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124, upload_time = "2025-03-02T00:00:14.518Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122, upload_time = "2025-03-02T00:00:17.212Z" }, - { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831, upload_time = "2025-03-02T00:00:19.696Z" }, - { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583, upload_time = "2025-03-02T00:00:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753, upload_time = "2025-03-02T00:00:25.038Z" }, - { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550, upload_time = "2025-03-02T00:00:26.929Z" }, - { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367, upload_time = "2025-03-02T00:00:28.735Z" }, - { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843, upload_time = "2025-03-02T00:00:30.592Z" }, - { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057, upload_time = "2025-03-02T00:00:33.393Z" }, - { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789, upload_time = "2025-03-02T00:00:36.009Z" }, - { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919, upload_time = "2025-03-02T00:00:38.581Z" }, - { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812, upload_time = "2025-03-02T00:00:42.934Z" }, - { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571, upload_time = "2025-03-02T00:00:46.026Z" }, - { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832, upload_time = "2025-03-02T00:00:48.647Z" }, - { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719, upload_time = "2025-03-02T00:00:51.397Z" }, - { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852, upload_time = "2025-03-02T00:00:53.317Z" }, - { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906, upload_time = "2025-03-02T00:00:56.49Z" }, - { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572, upload_time = "2025-03-02T00:00:59.995Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631, upload_time = "2025-03-02T00:01:01.623Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792, upload_time = "2025-03-02T00:01:04.133Z" }, - { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957, upload_time = "2025-03-02T00:01:06.987Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807, upload-time = "2025-03-02T00:01:37.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361, upload-time = "2025-03-02T00:00:06.528Z" }, + { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350, upload-time = "2025-03-02T00:00:09.537Z" }, + { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572, upload-time = "2025-03-02T00:00:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124, upload-time = "2025-03-02T00:00:14.518Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122, upload-time = "2025-03-02T00:00:17.212Z" }, + { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831, upload-time = "2025-03-02T00:00:19.696Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583, upload-time = "2025-03-02T00:00:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753, upload-time = "2025-03-02T00:00:25.038Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550, upload-time = "2025-03-02T00:00:26.929Z" }, + { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367, upload-time = "2025-03-02T00:00:28.735Z" }, + { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843, upload-time = "2025-03-02T00:00:30.592Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057, upload-time = "2025-03-02T00:00:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789, upload-time = "2025-03-02T00:00:36.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919, upload-time = "2025-03-02T00:00:38.581Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812, upload-time = "2025-03-02T00:00:42.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571, upload-time = "2025-03-02T00:00:46.026Z" }, + { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832, upload-time = "2025-03-02T00:00:48.647Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719, upload-time = "2025-03-02T00:00:51.397Z" }, + { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852, upload-time = "2025-03-02T00:00:53.317Z" }, + { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906, upload-time = "2025-03-02T00:00:56.49Z" }, + { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572, upload-time = "2025-03-02T00:00:59.995Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631, upload-time = "2025-03-02T00:01:01.623Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792, upload-time = "2025-03-02T00:01:04.133Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957, upload-time = "2025-03-02T00:01:06.987Z" }, ] [[package]] name = "dnspython" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload_time = "2024-10-05T20:14:59.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload_time = "2024-10-05T20:14:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] [[package]] @@ -236,9 +236,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload_time = "2025-03-13T11:52:43.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload_time = "2025-03-13T11:52:41.757Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, ] [[package]] @@ -249,18 +249,18 @@ dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload_time = "2024-06-20T11:30:30.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload_time = "2024-06-20T11:30:28.248Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload_time = "2024-07-12T22:26:00.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload_time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] [[package]] @@ -272,42 +272,42 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload_time = "2025-03-23T22:55:43.822Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload_time = "2025-03-23T22:55:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, ] [[package]] name = "greenlet" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload_time = "2024-09-20T18:21:04.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload-time = "2024-09-20T18:21:04.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload_time = "2024-09-20T17:08:26.312Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload_time = "2024-09-20T17:36:48.983Z" }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload_time = "2024-09-20T17:39:22.705Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload_time = "2024-09-20T17:44:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload_time = "2024-09-20T17:08:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload_time = "2024-09-20T17:08:36.85Z" }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload_time = "2024-09-20T17:44:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716, upload_time = "2024-09-20T17:09:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload_time = "2024-09-20T17:17:09.501Z" }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload_time = "2024-09-20T17:36:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload_time = "2024-09-20T17:39:24.55Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload_time = "2024-09-20T17:44:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload_time = "2024-09-20T17:08:47.852Z" }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload_time = "2024-09-20T17:08:38.079Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload_time = "2024-09-20T17:44:20.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload_time = "2024-09-20T17:09:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload-time = "2024-09-20T17:08:26.312Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload-time = "2024-09-20T17:36:48.983Z" }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload-time = "2024-09-20T17:39:22.705Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload-time = "2024-09-20T17:44:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload-time = "2024-09-20T17:08:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload-time = "2024-09-20T17:08:36.85Z" }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload-time = "2024-09-20T17:44:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716, upload-time = "2024-09-20T17:09:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload-time = "2024-09-20T17:17:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload-time = "2024-09-20T17:36:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload-time = "2024-09-20T17:39:24.55Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload-time = "2024-09-20T17:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload-time = "2024-09-20T17:08:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload-time = "2024-09-20T17:08:38.079Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload-time = "2024-09-20T17:44:20.556Z" }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload-time = "2024-09-20T17:09:28.753Z" }, ] [[package]] name = "h11" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload_time = "2022-09-25T15:40:01.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload_time = "2022-09-25T15:39:59.68Z" }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, ] [[package]] @@ -318,9 +318,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385, upload_time = "2025-04-11T14:42:46.661Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385, upload-time = "2025-04-11T14:42:46.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732, upload_time = "2025-04-11T14:42:44.896Z" }, + { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732, upload-time = "2025-04-11T14:42:44.896Z" }, ] [[package]] @@ -333,54 +333,54 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload_time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload_time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload_time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload_time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "jmespath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload_time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload_time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload_time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload_time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "passlib" version = "1.7.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload_time = "2020-10-08T19:00:52.121Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload_time = "2020-10-08T19:00:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, ] [package.optional-dependencies] @@ -392,46 +392,46 @@ bcrypt = [ name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload_time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload_time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "psycopg2-binary" version = "2.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload_time = "2024-10-16T11:24:58.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload_time = "2024-10-16T11:21:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload_time = "2024-10-16T11:21:51.989Z" }, - { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload_time = "2024-10-16T11:21:57.584Z" }, - { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload_time = "2024-10-16T11:22:02.005Z" }, - { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload_time = "2024-10-16T11:22:06.412Z" }, - { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload_time = "2024-10-16T11:22:11.583Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload_time = "2024-10-16T11:22:16.406Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload_time = "2024-10-16T11:22:21.366Z" }, - { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload_time = "2024-10-16T11:22:25.684Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload_time = "2024-10-16T11:22:30.562Z" }, - { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload_time = "2025-01-04T20:09:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, ] [[package]] name = "pyasn1" version = "0.4.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", size = 146820, upload_time = "2019-11-16T17:27:38.772Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", size = 146820, upload-time = "2019-11-16T17:27:38.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/1e/a94a8d635fa3ce4cfc7f506003548d0a2447ae76fd5ca53932970fe3053f/pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", size = 77145, upload_time = "2019-11-16T17:27:11.07Z" }, + { url = "https://files.pythonhosted.org/packages/62/1e/a94a8d635fa3ce4cfc7f506003548d0a2447ae76fd5ca53932970fe3053f/pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", size = 77145, upload-time = "2019-11-16T17:27:11.07Z" }, ] [[package]] name = "pycparser" version = "2.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload_time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload_time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] @@ -444,9 +444,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513, upload_time = "2025-04-08T13:27:06.399Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513, upload-time = "2025-04-08T13:27:06.399Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591, upload_time = "2025-04-08T13:27:03.789Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591, upload-time = "2025-04-08T13:27:03.789Z" }, ] [[package]] @@ -456,34 +456,34 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395, upload_time = "2025-04-02T09:49:41.8Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395, upload-time = "2025-04-02T09:49:41.8Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551, upload_time = "2025-04-02T09:47:51.648Z" }, - { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785, upload_time = "2025-04-02T09:47:53.149Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758, upload_time = "2025-04-02T09:47:55.006Z" }, - { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109, upload_time = "2025-04-02T09:47:56.532Z" }, - { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159, upload_time = "2025-04-02T09:47:58.088Z" }, - { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222, upload_time = "2025-04-02T09:47:59.591Z" }, - { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980, upload_time = "2025-04-02T09:48:01.397Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840, upload_time = "2025-04-02T09:48:03.056Z" }, - { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518, upload_time = "2025-04-02T09:48:04.662Z" }, - { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025, upload_time = "2025-04-02T09:48:06.226Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991, upload_time = "2025-04-02T09:48:08.114Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262, upload_time = "2025-04-02T09:48:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626, upload_time = "2025-04-02T09:48:11.288Z" }, - { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590, upload_time = "2025-04-02T09:48:12.861Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963, upload_time = "2025-04-02T09:48:14.553Z" }, - { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896, upload_time = "2025-04-02T09:48:16.222Z" }, - { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload_time = "2025-04-02T09:48:17.97Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551, upload-time = "2025-04-02T09:47:51.648Z" }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785, upload-time = "2025-04-02T09:47:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758, upload-time = "2025-04-02T09:47:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109, upload-time = "2025-04-02T09:47:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159, upload-time = "2025-04-02T09:47:58.088Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222, upload-time = "2025-04-02T09:47:59.591Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980, upload-time = "2025-04-02T09:48:01.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840, upload-time = "2025-04-02T09:48:03.056Z" }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518, upload-time = "2025-04-02T09:48:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025, upload-time = "2025-04-02T09:48:06.226Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991, upload-time = "2025-04-02T09:48:08.114Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262, upload-time = "2025-04-02T09:48:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626, upload-time = "2025-04-02T09:48:11.288Z" }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590, upload-time = "2025-04-02T09:48:12.861Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963, upload-time = "2025-04-02T09:48:14.553Z" }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896, upload-time = "2025-04-02T09:48:16.222Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload-time = "2025-04-02T09:48:17.97Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload_time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload_time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -497,9 +497,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload_time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload_time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -509,9 +509,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload_time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload_time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, ] [[package]] @@ -523,9 +523,9 @@ dependencies = [ { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload_time = "2025-06-12T10:47:47.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload_time = "2025-06-12T10:47:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -535,18 +535,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload_time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload_time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "python-dotenv" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload_time = "2025-03-25T10:14:56.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload_time = "2025-03-25T10:14:55.034Z" }, + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] [[package]] @@ -558,18 +558,18 @@ dependencies = [ { name = "pyasn1" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/a0/c49687cf40cb6128ea4e0559855aff92cd5ebd1a60a31c08526818c0e51e/python-jose-3.4.0.tar.gz", hash = "sha256:9a9a40f418ced8ecaf7e3b28d69887ceaa76adad3bcaa6dae0d9e596fec1d680", size = 92145, upload_time = "2025-02-18T17:26:41.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a0/c49687cf40cb6128ea4e0559855aff92cd5ebd1a60a31c08526818c0e51e/python-jose-3.4.0.tar.gz", hash = "sha256:9a9a40f418ced8ecaf7e3b28d69887ceaa76adad3bcaa6dae0d9e596fec1d680", size = 92145, upload-time = "2025-02-18T17:26:41.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/b0/2586ea6b6fd57a994ece0b56418cbe93fff0efb85e2c9eb6b0caf24a4e37/python_jose-3.4.0-py2.py3-none-any.whl", hash = "sha256:9c9f616819652d109bd889ecd1e15e9a162b9b94d682534c9c2146092945b78f", size = 34616, upload_time = "2025-02-18T17:26:40.826Z" }, + { url = "https://files.pythonhosted.org/packages/63/b0/2586ea6b6fd57a994ece0b56418cbe93fff0efb85e2c9eb6b0caf24a4e37/python_jose-3.4.0-py2.py3-none-any.whl", hash = "sha256:9c9f616819652d109bd889ecd1e15e9a162b9b94d682534c9c2146092945b78f", size = 34616, upload-time = "2025-02-18T17:26:40.826Z" }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload_time = "2024-12-16T19:45:46.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload_time = "2024-12-16T19:45:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] [[package]] @@ -579,9 +579,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711, upload_time = "2022-07-20T10:28:36.115Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711, upload-time = "2022-07-20T10:28:36.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315, upload_time = "2022-07-20T10:28:34.978Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315, upload-time = "2022-07-20T10:28:34.978Z" }, ] [[package]] @@ -591,27 +591,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/2b/5c9562795c2eb2b5f63536961754760c25bf0f34af93d36aa28dea2fb303/s3transfer-0.11.5.tar.gz", hash = "sha256:8c8aad92784779ab8688a61aefff3e28e9ebdce43142808eaa3f0b0f402f68b7", size = 149107, upload_time = "2025-04-17T19:23:19.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/2b/5c9562795c2eb2b5f63536961754760c25bf0f34af93d36aa28dea2fb303/s3transfer-0.11.5.tar.gz", hash = "sha256:8c8aad92784779ab8688a61aefff3e28e9ebdce43142808eaa3f0b0f402f68b7", size = 149107, upload-time = "2025-04-17T19:23:19.051Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/39/13402e323666d17850eca87e4cd6ecfcf9fd7809cac9efdcce10272fc29d/s3transfer-0.11.5-py3-none-any.whl", hash = "sha256:757af0f2ac150d3c75bc4177a32355c3862a98d20447b69a0161812992fe0bd4", size = 84782, upload_time = "2025-04-17T19:23:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/45/39/13402e323666d17850eca87e4cd6ecfcf9fd7809cac9efdcce10272fc29d/s3transfer-0.11.5-py3-none-any.whl", hash = "sha256:757af0f2ac150d3c75bc4177a32355c3862a98d20447b69a0161812992fe0bd4", size = 84782, upload-time = "2025-04-17T19:23:17.516Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload_time = "2024-12-04T17:35:28.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload_time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload_time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload_time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -622,17 +622,17 @@ dependencies = [ { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299, upload_time = "2025-03-27T17:52:31.876Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299, upload-time = "2025-03-27T17:52:31.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887, upload_time = "2025-03-27T18:40:05.461Z" }, - { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367, upload_time = "2025-03-27T18:40:07.182Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806, upload_time = "2025-03-27T18:51:29.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131, upload_time = "2025-03-27T18:50:31.616Z" }, - { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364, upload_time = "2025-03-27T18:51:31.336Z" }, - { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482, upload_time = "2025-03-27T18:50:33.201Z" }, - { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704, upload_time = "2025-03-27T18:46:00.193Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564, upload_time = "2025-03-27T18:46:01.442Z" }, - { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894, upload_time = "2025-03-27T18:40:43.796Z" }, + { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887, upload-time = "2025-03-27T18:40:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367, upload-time = "2025-03-27T18:40:07.182Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806, upload-time = "2025-03-27T18:51:29.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131, upload-time = "2025-03-27T18:50:31.616Z" }, + { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364, upload-time = "2025-03-27T18:51:31.336Z" }, + { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482, upload-time = "2025-03-27T18:50:33.201Z" }, + { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704, upload-time = "2025-03-27T18:46:00.193Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564, upload-time = "2025-03-27T18:46:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894, upload-time = "2025-03-27T18:40:43.796Z" }, ] [[package]] @@ -642,9 +642,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload_time = "2025-04-13T13:56:17.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload_time = "2025-04-13T13:56:16.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, ] [[package]] @@ -659,6 +659,7 @@ dependencies = [ { name = "botocore" }, { name = "cffi" }, { name = "click" }, + { name = "coverage" }, { name = "cryptography" }, { name = "dnspython" }, { name = "ecdsa" }, @@ -667,6 +668,7 @@ dependencies = [ { name = "fastapi" }, { name = "greenlet" }, { name = "h11" }, + { name = "httpx" }, { name = "idna" }, { name = "jmespath" }, { name = "passlib", extra = ["bcrypt"] }, @@ -675,6 +677,9 @@ dependencies = [ { name = "pycparser" }, { name = "pydantic" }, { name = "pydantic-core" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, { name = "python-dateutil" }, { name = "python-dotenv" }, { name = "python-jose" }, @@ -709,6 +714,7 @@ requires-dist = [ { name = "botocore", specifier = "==1.37.37" }, { name = "cffi", specifier = "==1.17.1" }, { name = "click", specifier = "==8.1.8" }, + { name = "coverage", specifier = ">=6.0" }, { name = "coverage", marker = "extra == 'testing'", specifier = ">=6.0" }, { name = "cryptography", specifier = "==44.0.2" }, { name = "dnspython", specifier = "==2.7.0" }, @@ -718,6 +724,7 @@ requires-dist = [ { name = "fastapi", specifier = "==0.115.12" }, { name = "greenlet", specifier = "==3.1.1" }, { name = "h11", specifier = "==0.14.0" }, + { name = "httpx", specifier = ">=0.24" }, { name = "httpx", marker = "extra == 'testing'", specifier = ">=0.24" }, { name = "idna", specifier = "==3.10" }, { name = "jmespath", specifier = "==1.0.1" }, @@ -727,8 +734,11 @@ requires-dist = [ { name = "pycparser", specifier = "==2.22" }, { name = "pydantic", specifier = "==2.11.3" }, { name = "pydantic-core", specifier = "==2.33.1" }, + { name = "pytest", specifier = ">=7.0" }, { name = "pytest", marker = "extra == 'testing'", specifier = ">=7.0" }, + { name = "pytest-asyncio", specifier = ">=0.20" }, { name = "pytest-asyncio", marker = "extra == 'testing'", specifier = ">=0.20" }, + { name = "pytest-cov", specifier = ">=4.0" }, { name = "pytest-cov", marker = "extra == 'testing'", specifier = ">=4.0" }, { name = "python-dateutil", specifier = "==2.9.0.post0" }, { name = "python-dotenv", specifier = "==1.1.0" }, @@ -751,9 +761,9 @@ provides-extras = ["testing"] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] @@ -763,18 +773,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload_time = "2025-02-25T17:27:59.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload_time = "2025-02-25T17:27:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, ] [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] [[package]] @@ -785,7 +795,7 @@ dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755, upload_time = "2025-04-13T13:48:04.305Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755, upload-time = "2025-04-13T13:48:04.305Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404, upload_time = "2025-04-13T13:48:02.408Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404, upload-time = "2025-04-13T13:48:02.408Z" }, ] From 5147f186b3707c03baa8ebc8b2e4d257467ae5be Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:42:13 +0200 Subject: [PATCH 07/28] create find all in repo inmemory for debug --- src/adapters/inmemory/repositories/profile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/adapters/inmemory/repositories/profile.py b/src/adapters/inmemory/repositories/profile.py index 5c02bf8..2dcf590 100644 --- a/src/adapters/inmemory/repositories/profile.py +++ b/src/adapters/inmemory/repositories/profile.py @@ -44,3 +44,6 @@ def find_all_users(self) -> List[DomainProfile]: def find_all_coachs(self) -> List[DomainProfile]: return [p for p in self._data.values() if "coach" in (p.roles or [])] + + def find_all(self) -> List[DomainProfile]: + return list(self._data.values()) \ No newline at end of file From a71e89a258c4244ba47a6bffdce560585e1701ec Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:42:24 +0200 Subject: [PATCH 08/28] rename it cause -_- --- src/entrypoints/api/deps/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entrypoints/api/deps/auth.py b/src/entrypoints/api/deps/auth.py index 920ccf2..ce34b5f 100644 --- a/src/entrypoints/api/deps/auth.py +++ b/src/entrypoints/api/deps/auth.py @@ -36,7 +36,7 @@ def require_owner_or_admin( if str(user_id) != str(profile_id) and "admin" not in roles: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail="Forbidden access" + detail="Access forbidden" ) return user From 54d4f052e382c86d198eaa392feb4ea74b378df7 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:42:42 +0200 Subject: [PATCH 09/28] remove the older conftest cause is what shit --- src/entrypoints/api/tests/conftest.py | 81 ++++++++++----------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/src/entrypoints/api/tests/conftest.py b/src/entrypoints/api/tests/conftest.py index c53ec59..1c1525a 100644 --- a/src/entrypoints/api/tests/conftest.py +++ b/src/entrypoints/api/tests/conftest.py @@ -1,61 +1,42 @@ import pytest import os -from uuid import uuid4 - -import pytest_asyncio from httpx import AsyncClient, ASGITransport - from src.main import app from src.container import Container -from src.domain.model.profile import Profile as DomainProfile -from src.domain.model.group import Group as DomainGroup - -@pytest.fixture(autouse=True) -def seed_data(monkeypatch): - # 1) Forcer ENV=test et container unique - monkeypatch.setenv("ENV", "test") - c = Container(env="test") - monkeypatch.setattr("src.container.container", c) - - # helper pour créer et seed des profils - def _mk(email: str, pw: str, roles: list[str]): - h = c.hasher.hash(pw) - p = DomainProfile( - id=uuid4(), email=email, password=h, - name=email.split("@")[0], sex=None, age=None, - contact=None, pricing=None, description=None, - legacy=False, roles=roles, created_at=None - ) - c.profile_repo.add(p) - return p, pw - - admin = _mk("admin@example.com", "AdminPass123!", ["admin"]) - coach = _mk("coach@example.com", "CoachPass123!", ["coach"]) - user = _mk("user@example.com", "UserPass123!", ["user"]) - - # Seed un groupe pour le coach - grp = DomainGroup( - id=uuid4(), - owner_id=coach[0].id, - name="GroupeTest", - description="Pour tests", - created_at=None - ) - c.group_repo.add(grp) - - return { - "container": c, - "admin": admin, - "coach": coach, - "user": user, - "group": grp, - } +import pytest_asyncio + + + +# Avant tout, on définit la variable d'environnement +@pytest.fixture(scope="session", autouse=True) +def set_test_env(): + os.environ["ENV"] = "test" # Configure l'environnement à "test" + yield + # Optionnel : tu peux aussi réinitialiser la variable d'environnement après les tests si nécessaire + del os.environ["ENV"] + + +# Fixture pour initialiser un conteneur unique pour tous les tests +@pytest.fixture(scope="module", autouse=True) +def container(): + # Créer un conteneur en mémoire avec ENV='test' + c = Container(env="test") # Assure que le conteneur utilise l'environnement 'test' + + # Assurer que le conteneur utilise une base en mémoire + assert os.getenv("ENV") == "test", "L'environnement n'est pas correctement configuré sur 'test'." + + # Retourner le conteneur pour qu'il soit partagé dans tous les tests + return c + +# Fournit un client HTTPX asynchrone monté sur l'ASGI de FastAPI @pytest_asyncio.fixture -async def client(): +async def client(container): """ - Fournit un AsyncClient HTTPX monté sur l'ASGI FastAPI. + Fournit un AsyncClient HTTPX monté sur l'ASGI FastAPI avec un environnement de test. """ transport = ASGITransport(app=app) + + # Créer et retourner un client HTTPX pour les tests async with AsyncClient(transport=transport, base_url="http://testserver") as ac: - yield ac + yield ac # Retourne le client pour les tests From 4f1361b6db2c58a5fb307fab4525b8e8648f9489 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Tue, 22 Jul 2025 22:43:02 +0200 Subject: [PATCH 10/28] remove odler test and create some test for profile (maybe new fiew more) --- src/entrypoints/api/tests/profiles.py | 260 +++++++++----------------- 1 file changed, 89 insertions(+), 171 deletions(-) diff --git a/src/entrypoints/api/tests/profiles.py b/src/entrypoints/api/tests/profiles.py index b09e51f..2596d66 100644 --- a/src/entrypoints/api/tests/profiles.py +++ b/src/entrypoints/api/tests/profiles.py @@ -1,11 +1,17 @@ -# src/entrypoints/api/tests/test_profiles.py import pytest from uuid import uuid4 +from container import container @pytest.mark.asyncio class TestProfiles: + admin_token = "" + user_token = "" + user_uuid = "" async def test_create_profile_success(self, client): + global user_token + global user_uuid + payload = { "email": "alice@example.com", "password": "Secret123!", @@ -14,9 +20,14 @@ async def test_create_profile_success(self, client): "sex": "F", "age": 28 } + r = await client.post("/profiles", json=payload) assert r.status_code == 201 body = r.json() + + user_token = body["token"]["access_token"] + user_uuid = body["profile"]["id"] + assert "profile" in body and "token" in body assert body["profile"]["email"] == "alice@example.com" @@ -27,11 +38,30 @@ async def test_create_duplicate_email_returns_400(self, client): "confirm_password": "Pwd123!", "name": "Bob" } + r1 = await client.post("/profiles", json=payload) assert r1.status_code == 201 + r2 = await client.post("/profiles", json=payload) assert r2.status_code == 400 - assert "duplicate" in r2.json()["detail"].lower() + assert "already exists" in r2.json()["detail"].lower() + + async def test_admin_login(self, client): + global admin_token + + admin_email = "admin@mail.fr" + admin_password = "123456789" + + r = await client.post( + "/profiles/login", + json={"email": admin_email, "password": admin_password} + ) + + assert r.status_code == 200 + login_data = r.json() + assert "access_token" in login_data, f"Access token is missing. Response: {login_data}" + + admin_token = login_data["access_token"] async def test_login_and_get_me(self, client): data = { @@ -40,6 +70,7 @@ async def test_login_and_get_me(self, client): "confirm_password": "TopSecret!", "name": "Charlie" } + r = await client.post("/profiles", json=data) token = r.json()["token"]["access_token"] headers = {"Authorization": f"Bearer {token}"} @@ -47,192 +78,79 @@ async def test_login_and_get_me(self, client): assert r2.status_code == 200 assert r2.json()["email"] == "charlie@example.com" - async def test_login_bad_credentials_returns_401(self, client, seed_data): - admin, _ = seed_data["admin"] - r = await client.post( - "/profiles/login", - json={"email": admin.email, "password": "wrong!"} - ) - assert r.status_code == 401 + async def test_get_all_users_success(self, client): + global admin_token - async def test_get_all_users_requires_admin_and_returns_list(self, client, seed_data): - # sans token → 403 - r1 = await client.get("/profiles/users") - assert r1.status_code == 403 + r = await client.get("/profiles/users", headers={"Authorization": f"Bearer {admin_token}"}) - # avec token admin - admin, admin_pw = seed_data["admin"] - r_login = await client.post( - "/profiles/login", - json={"email": admin.email, "password": admin_pw} - ) - token = r_login.json()["access_token"] - r2 = await client.get( - "/profiles/users", - headers={"Authorization": f"Bearer {token}"} - ) - assert r2.status_code == 200 - users = r2.json() - assert any(u["email"] == admin.email for u in users) + assert r.status_code == 200 + users = r.json() + assert isinstance(users, list) - async def test_get_coach_profiles_empty_and_after_creation(self, client): - # d'abord, aucun coach - r1 = await client.get("/profiles/coachs") - assert r1.status_code == 200 - assert r1.json() == [] + async def test_get_all_users_forbidden_for_non_admin(self, client): + global user_token - # on en crée un - payload = { - "email": "coach2@example.com", - "password": "Coach123!", - "confirm_password": "Coach123!", - "name": "Coach2" - } - r2 = await client.post("/profiles", json=payload) - assert r2.status_code == 201 + r = await client.get("/profiles/users", headers={"Authorization": f"Bearer {user_token}"}) - # puis on vérifie - r3 = await client.get("/profiles/coachs") - emails = [c["email"] for c in r3.json()] - assert "coach2@example.com" in emails + assert r.status_code == 403 + assert "access forbidden" in r.json()["detail"].lower() - async def test_delete_profile_owner_or_admin(self, client, seed_data): - # crée Dave - r = await client.post( - "/profiles", - json={"email": "dave@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} - ) - pid = r.json()["profile"]["id"] - token_owner = r.json()["token"]["access_token"] + async def test_delete_profile_forbidden_for_non_owner_or_admin(self, client): + global user_token - # crée Eve - r_e = await client.post( - "/profiles", - json={"email": "eve@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} - ) - token_e = r_e.json()["token"]["access_token"] + r = await client.delete("/profiles/874c4345-d070-49a0-9275-e4a2189d1f45", headers={"Authorization": f"Bearer {user_token}"}) - # forbidden pour Eve - r_f = await client.delete( - f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_e}"} - ) - assert r_f.status_code == 403 + assert r.status_code == 403 + assert "access forbidden" in r.json()["detail"].lower() - # suppression par le propriétaire - r_ok = await client.delete( - f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_owner}"} - ) - assert r_ok.status_code == 204 + async def test_login_success(self, client): + login_data = { + "email": "alice@example.com", + "password": "Secret123!" + } - # tentative de suppressions ultérieure → 404 - r_nf = await client.delete( - f"/profiles/{pid}", headers={"Authorization": f"Bearer {token_owner}"} - ) - assert r_nf.status_code == 404 + r = await client.post("/profiles/login", json=login_data) - async def test_patch_profile_and_404(self, client): - fake_id = str(uuid4()) - r_nf = await client.patch(f"/profiles/{fake_id}", json={"name": "New"}) - assert r_nf.status_code in (403, 404) + assert r.status_code == 200 + assert "access_token" in r.json() - # création puis patch réussi - r = await client.post( - "/profiles", - json={"email": "guy@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} - ) - pid = r.json()["profile"]["id"] - token = r.json()["token"]["access_token"] + async def test_login_failed_wrong_password(self, client): + login_data = { + "email": "alice@example.com", + "password": "WrongPassword" + } - r_patch = await client.patch( - f"/profiles/{pid}", - json={"name": "Guy Updated"}, - headers={"Authorization": f"Bearer {token}"} - ) - assert r_patch.status_code == 200 - assert r_patch.json()["name"] == "Guy Updated" + r = await client.post("/profiles/login", json=login_data) - async def test_patch_email_duplicate_and_notfound(self, client): - # on crée deux profils - r1 = await client.post( - "/profiles", json={"email": "foo@example.com", "password": "A1!", "confirm_password": "A1!"} - ) - r2 = await client.post( - "/profiles", json={"email": "bar@example.com", "password": "B2!", "confirm_password": "B2!"} - ) - id1 = r1.json()["profile"]["id"] - token = r2.json()["token"]["access_token"] - - # on tente un duplicate email - r_dup = await client.patch( - f"/profiles/{id1}/email", - json={"email": "foo@example.com"}, - headers={"Authorization": f"Bearer {token}"} - ) - assert r_dup.status_code == 400 - - # on tente sur ID inexistant - fake = str(uuid4()) - r_nf = await client.patch( - f"/profiles/{fake}/email", - json={"email": "x@y.com"}, - headers={"Authorization": f"Bearer {token}"} - ) - assert r_nf.status_code == 404 + assert r.status_code == 401 + assert "Invalid password or email" in r.json()["detail"] - async def test_patch_password(self, client): - # création - r = await client.post( - "/profiles", - json={"email": "harry@example.com", "password": "OldPass!", "confirm_password": "OldPass!"} - ) - pid = r.json()["profile"]["id"] - token = r.json()["token"]["access_token"] + async def test_patch_profile_success(self, client): + global user_token + global user_uuid - # mauvais ancien mot de passe - r_bad = await client.patch( - f"/profiles/{pid}/password", - json={"old_password": "Bad!", "new_password": "NewPass!"}, - headers={"Authorization": f"Bearer {token}"} - ) - assert r_bad.status_code == 400 + update_data = { + "name": "Updated User" + } - # correct - r_ok = await client.patch( - f"/profiles/{pid}/password", - json={"old_password": "OldPass!", "new_password": "NewPass!"}, - headers={"Authorization": f"Bearer {token}"} - ) - assert r_ok.status_code == 204 + r = await client.patch(f"/profiles/{user_uuid}", json=update_data, headers={"Authorization": f"Bearer {user_token}"}) - async def test_patch_roles_protected_and_success(self, client, seed_data): - # création d'un user lambda - r = await client.post( - "/profiles", - json={"email": "ian@example.com", "password": "Pwd!", "confirm_password": "Pwd!"} - ) - pid = r.json()["profile"]["id"] - token_user = r.json()["token"]["access_token"] - - # il ne peut pas se donner des rôles - r_403 = await client.patch( - f"/profiles/{pid}/roles", - json={"roles": ["admin"]}, - headers={"Authorization": f"Bearer {token_user}"} - ) - assert r_403.status_code == 403 + assert r.status_code == 200 + assert r.json()["name"] == "Updated User" - # l'admin peut - admin, admin_pw = seed_data["admin"] - r_login = await client.post( - "/profiles/login", - json={"email": admin.email, "password": admin_pw} - ) - token_admin = r_login.json()["access_token"] + async def test_patch_profile_forbidden(self, client): + global user_token - r_admin = await client.patch( - f"/profiles/{pid}/roles", - json={"roles": ["coach"]}, - headers={"Authorization": f"Bearer {token_admin}"} - ) - assert r_admin.status_code == 200 - assert "coach" in r_admin.json()["roles"] + update_data = {"name": "Updated Name"} + r = await client.patch("/profiles/874c4345-d070-49a0-9275-e4a2189d1f45", json=update_data, headers={"Authorization": f"Bearer {user_token}"}) + + assert r.status_code == 403 + assert "Access forbidden" in r.json()["detail"] + + async def test_delete_profile_success_as_owner(self, client): + global user_token + global user_uuid + + r = await client.delete(f"/profiles/{user_uuid}", headers={"Authorization": f"Bearer {user_token}"}) + + assert r.status_code == 204 From 41f9f9569d9f9121028b980b41e04ad72264d976 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 23 Jul 2025 16:11:56 +0200 Subject: [PATCH 11/28] update schema for optional data for coachprofilread --- src/entrypoints/api/schemas/profile.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/entrypoints/api/schemas/profile.py b/src/entrypoints/api/schemas/profile.py index d26d884..cd608fa 100644 --- a/src/entrypoints/api/schemas/profile.py +++ b/src/entrypoints/api/schemas/profile.py @@ -34,13 +34,13 @@ class ProfileRead(BaseModel): class CoachProfileRead(BaseModel): id: UUID - name: str - sex: str - age: int - contact: str - pricing: float - description: str - legacy: str + name: Optional[str] = None + sex: Optional[str] = None + age: Optional[int] = None + contact: Optional[str] = None + pricing: Optional[float] = None + description: Optional[str] = None + legacy: Optional[str] = None model_config = { "from_attributes": True From ebef58f392b18ab8ec4f6baed4c7785f07c55433 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 23 Jul 2025 16:12:11 +0200 Subject: [PATCH 12/28] create scenario profil test --- src/entrypoints/api/tests/profiles.py | 265 +++++++++++++++----------- 1 file changed, 159 insertions(+), 106 deletions(-) diff --git a/src/entrypoints/api/tests/profiles.py b/src/entrypoints/api/tests/profiles.py index 2596d66..7ca3d93 100644 --- a/src/entrypoints/api/tests/profiles.py +++ b/src/entrypoints/api/tests/profiles.py @@ -1,17 +1,14 @@ import pytest -from uuid import uuid4 -from container import container @pytest.mark.asyncio -class TestProfiles: +class TestProfilesScenario: admin_token = "" user_token = "" + coach_token = "" user_uuid = "" + coach_uuid = "" - async def test_create_profile_success(self, client): - global user_token - global user_uuid - + async def test_01_create_user_profile(self, client): payload = { "email": "alice@example.com", "password": "Secret123!", @@ -20,137 +17,193 @@ async def test_create_profile_success(self, client): "sex": "F", "age": 28 } - r = await client.post("/profiles", json=payload) assert r.status_code == 201 body = r.json() + self.__class__.user_token = body["token"]["access_token"] + self.__class__.user_uuid = body["profile"]["id"] + assert body["profile"]["roles"] == ["user"] - user_token = body["token"]["access_token"] - user_uuid = body["profile"]["id"] - - assert "profile" in body and "token" in body - assert body["profile"]["email"] == "alice@example.com" - - async def test_create_duplicate_email_returns_400(self, client): + async def test_02_create_coach_profile(self, client): payload = { - "email": "bob@example.com", - "password": "Pwd123!", - "confirm_password": "Pwd123!", - "name": "Bob" + "email": "coach@example.com", + "password": "CoachPass123!", + "confirm_password": "CoachPass123!", + "name": "Coach", + "sex": "M", + "age": 35 } + r = await client.post("/profiles", json=payload) + assert r.status_code == 201 + body = r.json() + self.__class__.coach_token = body["token"]["access_token"] + self.__class__.coach_uuid = body["profile"]["id"] + assert body["profile"]["roles"] == ["user"] - r1 = await client.post("/profiles", json=payload) - assert r1.status_code == 201 - - r2 = await client.post("/profiles", json=payload) - assert r2.status_code == 400 - assert "already exists" in r2.json()["detail"].lower() - - async def test_admin_login(self, client): - global admin_token - - admin_email = "admin@mail.fr" - admin_password = "123456789" - + async def test_03_admin_login(self, client): r = await client.post( "/profiles/login", - json={"email": admin_email, "password": admin_password} + json={"email": "admin@mail.fr", "password": "123456789"} ) - assert r.status_code == 200 - login_data = r.json() - assert "access_token" in login_data, f"Access token is missing. Response: {login_data}" - - admin_token = login_data["access_token"] - - async def test_login_and_get_me(self, client): - data = { - "email": "charlie@example.com", - "password": "TopSecret!", - "confirm_password": "TopSecret!", - "name": "Charlie" - } - - r = await client.post("/profiles", json=data) - token = r.json()["token"]["access_token"] - headers = {"Authorization": f"Bearer {token}"} - r2 = await client.get("/profiles/me", headers=headers) - assert r2.status_code == 200 - assert r2.json()["email"] == "charlie@example.com" - - async def test_get_all_users_success(self, client): - global admin_token - - r = await client.get("/profiles/users", headers={"Authorization": f"Bearer {admin_token}"}) - + self.__class__.admin_token = r.json()["access_token"] + + async def test_04_admin_promote_coach(self, client): + update_data = {"roles": ["user", "coach"]} + r = await client.patch( + f"/profiles/{self.__class__.coach_uuid}/roles", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.admin_token}"} + ) assert r.status_code == 200 - users = r.json() - assert isinstance(users, list) - - async def test_get_all_users_forbidden_for_non_admin(self, client): - global user_token - - r = await client.get("/profiles/users", headers={"Authorization": f"Bearer {user_token}"}) - + roles = r.json()["roles"] + # On ne vérifie plus "user", seulement que "coach" a bien été ajouté + assert "coach" in roles + + async def test_05_coach_cannot_update_roles(self, client): + # un coach ne peut pas toucher aux rôles + update_data = {"roles": ["admin"]} + r = await client.patch( + f"/profiles/{self.__class__.user_uuid}/roles", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.coach_token}"} + ) assert r.status_code == 403 - assert "access forbidden" in r.json()["detail"].lower() - - async def test_delete_profile_forbidden_for_non_owner_or_admin(self, client): - global user_token - - r = await client.delete("/profiles/874c4345-d070-49a0-9275-e4a2189d1f45", headers={"Authorization": f"Bearer {user_token}"}) + async def test_06_user_cannot_update_roles(self, client): + # un user non-admin ne peut pas toucher aux rôles + update_data = {"roles": ["admin"]} + r = await client.patch( + f"/profiles/{self.__class__.user_uuid}/roles", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.user_token}"} + ) assert r.status_code == 403 - assert "access forbidden" in r.json()["detail"].lower() - async def test_login_success(self, client): - login_data = { + async def test_07_create_duplicate_user_profile_returns_400(self, client): + payload = { "email": "alice@example.com", - "password": "Secret123!" + "password": "Secret123!", + "confirm_password": "Secret123!", + "name": "Alice Dup" } + r = await client.post("/profiles", json=payload) + assert r.status_code == 400 + assert "already exists" in r.json()["detail"].lower() + + async def test_08_coach_cannot_update_user_profile(self, client): + update_data = {"name": "HackerName"} + r = await client.patch( + f"/profiles/{self.__class__.user_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.coach_token}"} + ) + assert r.status_code == 403 - r = await client.post("/profiles/login", json=login_data) - + async def test_09_admin_can_update_user_profile(self, client): + update_data = {"name": "AdminUpdatedName"} + r = await client.patch( + f"/profiles/{self.__class__.user_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.admin_token}"} + ) assert r.status_code == 200 - assert "access_token" in r.json() - - async def test_login_failed_wrong_password(self, client): - login_data = { - "email": "alice@example.com", - "password": "WrongPassword" - } + assert r.json()["name"] == "AdminUpdatedName" + + async def test_10_user_can_update_own_profile(self, client): + update_data = {"name": "UserUpdatedName"} + r = await client.patch( + f"/profiles/{self.__class__.user_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {self.__class__.user_token}"} + ) + assert r.status_code == 200 + assert r.json()["name"] == "UserUpdatedName" - r = await client.post("/profiles/login", json=login_data) + async def test_11_coach_get_me_success(self, client): + r = await client.get( + "/profiles/me", + headers={"Authorization": f"Bearer {self.__class__.coach_token}"} + ) + assert r.status_code == 200 + assert r.json()["email"] == "coach@example.com" + async def test_12_coach_get_me_with_invalid_token(self, client): + r = await client.get( + "/profiles/me", + headers={"Authorization": "Bearer invalidtoken"} + ) assert r.status_code == 401 - assert "Invalid password or email" in r.json()["detail"] - async def test_patch_profile_success(self, client): - global user_token - global user_uuid + async def test_13_public_get_coaches(self, client): + r = await client.get("/profiles/coachs") + assert r.status_code == 200 + assert isinstance(r.json(), list) - update_data = { - "name": "Updated User" - } + async def test_14_user_cannot_get_all_users(self, client): + r = await client.get( + "/profiles/users", + headers={"Authorization": f"Bearer {self.__class__.user_token}"} + ) + assert r.status_code == 403 - r = await client.patch(f"/profiles/{user_uuid}", json=update_data, headers={"Authorization": f"Bearer {user_token}"}) + async def test_15_coach_login_succes(self, client): + r = await client.post( + "/profiles/login", + json={"email": "coach@example.com", "password": "CoachPass123!"} + ) + assert r.status_code == 200 + data = r.json() + assert "access_token" in data + self.__class__.coach_token = data["access_token"] + assert "access_token" in r.json() + + async def test_15_coach_can_get_all_users(self, client): + r = await client.get( + "/profiles/users", + headers={"Authorization": f"Bearer {self.__class__.coach_token}"} + ) + assert r.status_code == 200 + async def test_16_admin_can_get_all_users(self, client): + r = await client.get( + "/profiles/users", + headers={"Authorization": f"Bearer {self.__class__.admin_token}"} + ) assert r.status_code == 200 - assert r.json()["name"] == "Updated User" - async def test_patch_profile_forbidden(self, client): - global user_token + async def test_17_admin_login_wrong_password(self, client): + r = await client.post( + "/profiles/login", + json={"email": "admin@mail.fr", "password": "wrongpass"} + ) + assert r.status_code == 401 - update_data = {"name": "Updated Name"} - r = await client.patch("/profiles/874c4345-d070-49a0-9275-e4a2189d1f45", json=update_data, headers={"Authorization": f"Bearer {user_token}"}) + async def test_18_user_login_success(self, client): + r = await client.post( + "/profiles/login", + json={"email": "alice@example.com", "password": "Secret123!"} + ) + assert r.status_code == 200 + assert "access_token" in r.json() + async def test_19_coach_cannot_delete_user(self, client): + r = await client.delete( + f"/profiles/{self.__class__.user_uuid}", + headers={"Authorization": f"Bearer {self.__class__.coach_token}"} + ) assert r.status_code == 403 - assert "Access forbidden" in r.json()["detail"] - - async def test_delete_profile_success_as_owner(self, client): - global user_token - global user_uuid - r = await client.delete(f"/profiles/{user_uuid}", headers={"Authorization": f"Bearer {user_token}"}) + async def test_20_user_can_delete_own_profile(self, client): + r = await client.delete( + f"/profiles/{self.__class__.user_uuid}", + headers={"Authorization": f"Bearer {self.__class__.user_token}"} + ) + assert r.status_code == 204 + async def test_21_admin_can_delete_coach(self, client): + r = await client.delete( + f"/profiles/{self.__class__.coach_uuid}", + headers={"Authorization": f"Bearer {self.__class__.admin_token}"} + ) assert r.status_code == 204 From c2445d289a76d71b8ef6c19e0317f605ca2832ac Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 09:46:00 +0200 Subject: [PATCH 13/28] update inmemory group for using profils inmemory repo --- src/adapters/inmemory/repositories/group.py | 4 ++-- src/container.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapters/inmemory/repositories/group.py b/src/adapters/inmemory/repositories/group.py index e910625..76f4b82 100644 --- a/src/adapters/inmemory/repositories/group.py +++ b/src/adapters/inmemory/repositories/group.py @@ -9,10 +9,10 @@ from src.adapters.inmemory.repositories.profile import InMemoryProfileRepository class InMemoryGroupRepository(GroupRepository): - def __init__(self): + def __init__(self, profile_repo: InMemoryProfileRepository): self._groups: dict[UUID, DomainGroup] = {} self._members: dict[UUID, List[UUID]] = {} - self._profile_repo = InMemoryProfileRepository() + self._profile_repo = profile_repo def find_by_id(self, id: UUID) -> Optional[DomainGroup]: return self._groups.get(id) diff --git a/src/container.py b/src/container.py index 9228f83..63a3e31 100644 --- a/src/container.py +++ b/src/container.py @@ -37,7 +37,7 @@ def __init__(self, env: str | None = None): from src.adapters.inmemory.repositories.exercise import InMemoryExerciseRepository from src.adapters.inmemory.repositories.diet import InMemoryDietRepository self.profile_repo = InMemoryProfileRepository(initial=[admin]) - self.group_repo = InMemoryGroupRepository() + self.group_repo = InMemoryGroupRepository(self.profile_repo) self.training_repo = InMemoryTrainingRepository() self.exercise_repo = InMemoryExerciseRepository() self.diet_repo = InMemoryDietRepository() From b59238dbc7d8ea7c961147a888843b8bd3c28fe8 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 09:46:29 +0200 Subject: [PATCH 14/28] fix the probleme when remove member groups 404 --- src/domain/services/group.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/domain/services/group.py b/src/domain/services/group.py index f0e1f36..05bcb38 100644 --- a/src/domain/services/group.py +++ b/src/domain/services/group.py @@ -54,7 +54,9 @@ def remove_member(self, group_id: UUID, user_id: UUID) -> None: if not group: raise NotFoundError(f"Group with id {group_id} not found") - if user_id not in self._repo.list_members(group_id): + profiles = self._repo.list_members(group_id) + member_ids = [p.id for p in profiles] + if user_id not in member_ids: raise NotFoundError(f"User with id {user_id} is not a member of group {group_id}") self._repo.remove_member(group_id, user_id) From 4cde95968573fc4a818f982d1d90d37ac335371a Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 09:47:00 +0200 Subject: [PATCH 15/28] create 26 test for testing groups still in progress --- src/entrypoints/api/tests/conftest.py | 1 - src/entrypoints/api/tests/group.py | 299 ++++++++++++++++++ src/tracknatrainapi.egg-info/PKG-INFO | 52 +++ src/tracknatrainapi.egg-info/SOURCES.txt | 60 ++++ .../dependency_links.txt | 1 + src/tracknatrainapi.egg-info/requires.txt | 49 +++ src/tracknatrainapi.egg-info/top_level.txt | 6 + 7 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 src/entrypoints/api/tests/group.py create mode 100644 src/tracknatrainapi.egg-info/PKG-INFO create mode 100644 src/tracknatrainapi.egg-info/SOURCES.txt create mode 100644 src/tracknatrainapi.egg-info/dependency_links.txt create mode 100644 src/tracknatrainapi.egg-info/requires.txt create mode 100644 src/tracknatrainapi.egg-info/top_level.txt diff --git a/src/entrypoints/api/tests/conftest.py b/src/entrypoints/api/tests/conftest.py index 1c1525a..ddd1787 100644 --- a/src/entrypoints/api/tests/conftest.py +++ b/src/entrypoints/api/tests/conftest.py @@ -13,7 +13,6 @@ def set_test_env(): os.environ["ENV"] = "test" # Configure l'environnement à "test" yield # Optionnel : tu peux aussi réinitialiser la variable d'environnement après les tests si nécessaire - del os.environ["ENV"] # Fixture pour initialiser un conteneur unique pour tous les tests diff --git a/src/entrypoints/api/tests/group.py b/src/entrypoints/api/tests/group.py new file mode 100644 index 0000000..b9d3d3c --- /dev/null +++ b/src/entrypoints/api/tests/group.py @@ -0,0 +1,299 @@ + +import pytest +import uuid + +@pytest.mark.asyncio +class TestGroupsScenario: + admin_token: str = "" + user_token: str = "" + coach_token: str = "" + user_uuid: str = "" + coach_uuid: str = "" + coach_group_uuid: str = "" + admin_group_uuid: str = "" + + # 01 – création d’un user + async def test_01_create_user_profile(self, client): + payload = { + "email": "alice@example.com", + "password": "Secret123!", + "confirm_password": "Secret123!", + "name": "Alice", + "sex": "F", + "age": 28 + } + r = await client.post("/profiles", json=payload) + assert r.status_code == 201 + body = r.json() + TestGroupsScenario.user_token = body["token"]["access_token"] + TestGroupsScenario.user_uuid = body["profile"]["id"] + + # 02 – création d’un coach (initialement role=["user"]) + async def test_02_create_coach_profile(self, client): + payload = { + "email": "coach@example.com", + "password": "CoachPass123!", + "confirm_password": "CoachPass123!", + "name": "Coach", + "sex": "M", + "age": 35 + } + r = await client.post("/profiles", json=payload) + assert r.status_code == 201 + body = r.json() + TestGroupsScenario.coach_token = body["token"]["access_token"] + TestGroupsScenario.coach_uuid = body["profile"]["id"] + + # 03 – login admin + async def test_03_admin_login(self, client): + r = await client.post( + "/profiles/login", + json={"email": "admin@mail.fr", "password": "123456789"} + ) + assert r.status_code == 200 + TestGroupsScenario.admin_token = r.json()["access_token"] + + # 04 – promotion du coach → roles=["user","coach"] + async def test_04_admin_promote_coach(self, client): + update_data = {"roles": ["user", "coach"]} + r = await client.patch( + f"/profiles/{TestGroupsScenario.coach_uuid}/roles", + json=update_data, + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 200 + roles = r.json()["roles"] + assert "coach" in roles + + # 05 – relogin coach pour avoir le nouveau token + async def test_05_coach_login_success(self, client): + r = await client.post( + "/profiles/login", + json={"email": "coach@example.com", "password": "CoachPass123!"} + ) + assert r.status_code == 200 + data = r.json() + assert "access_token" in data + TestGroupsScenario.coach_token = data["access_token"] + + # 06 – coach crée un groupe + async def test_06_coach_create_group(self, client): + payload = {"name": "CoachGroup", "description": "Groupe du coach"} + r = await client.post( + "/groups", + json=payload, + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 201 + grp = r.json() + TestGroupsScenario.coach_group_uuid = grp["id"] + assert grp["name"] == "CoachGroup" + + # 07 – coach lit un groupe inexistant → 404 + async def test_07_coach_get_group_not_found(self, client): + fake = str(uuid.UUID(int=0)) + r = await client.get( + f"/groups/{fake}", + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 404 + + # 08 – coach lit son groupe → 200 + async def test_08_coach_get_group_success(self, client): + r = await client.get( + f"/groups/{TestGroupsScenario.coach_group_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 200 + assert r.json()["id"] == TestGroupsScenario.coach_group_uuid + + # 09 – coach met à jour son groupe → 200 + async def test_09_coach_update_group(self, client): + update_data = {"name": "CoachGroupNew", "description": "Desc maj"} + r = await client.patch( + f"/groups/{TestGroupsScenario.coach_group_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 200 + body = r.json() + assert body["name"] == "CoachGroupNew" + + # 10 – user tente de mettre à jour le groupe du coach → 403 + async def test_10_user_update_group_forbidden(self, client): + update_data = {"name": "Hack"} + r = await client.patch( + f"/groups/{TestGroupsScenario.coach_group_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 403 + + # 11 – admin met à jour le groupe du coach → 200 + async def test_11_admin_update_group(self, client): + update_data = {"description": "Admin update"} + r = await client.patch( + f"/groups/{TestGroupsScenario.coach_group_uuid}", + json=update_data, + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 200 + assert r.json()["description"] == "Admin update" + + # 12 – user avec token invalide lit la liste → 401 + async def test_12_user_get_groups_invalid_token(self, client): + r = await client.get( + "/groups", + headers={"Authorization": "Bearer invalidtoken"} + ) + assert r.status_code == 401 + + # 13 – user lit tous les groupes → 200 + async def test_13_user_list_groups(self, client): + r = await client.get( + "/groups", + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # 14 – user tente de créer un groupe → 403 + async def test_14_user_create_group_forbidden(self, client): + payload = {"name": "UserGroup", "description": "Desc"} + r = await client.post( + "/groups", + json=payload, + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 403 + + # 15 – admin crée un groupe + async def test_15_admin_create_group(self, client): + payload = {"name": "AdminGroup", "description": "Groupe admin"} + r = await client.post( + "/groups", + json=payload, + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 201 + grp = r.json() + TestGroupsScenario.admin_group_uuid = grp["id"] + + # 16 – coach supprime le groupe admin → 403 + async def test_16_coach_delete_admin_group_forbidden(self, client): + r = await client.delete( + f"/groups/{TestGroupsScenario.admin_group_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 403 + + # 17 – admin supprime son propre groupe → 204 + async def test_17_admin_delete_admin_group_success(self, client): + r = await client.delete( + f"/groups/{TestGroupsScenario.admin_group_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 204 + + # 18 – coach ajoute l’utilisateur au groupe → 204 + async def test_18_coach_add_user_to_group(self, client): + r = await client.post( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 204 + + # 19 – admin retire l’utilisateur du groupe → 204 + async def test_19_admin_remove_user_from_group(self, client): + r = await client.delete( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 204, f"Failed to add member: { r.text }" + + + # 20 – admin ré-ajoute l’utilisateur → 204 + async def test_20_admin_add_user_to_group(self, client): + r = await client.post( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 204 + + # 21 – user tente de lister les membres → 403 + async def test_21_user_list_members_forbidden(self, client): + r = await client.get( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members", + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 403 + + # 22 – admin liste les membres → 200 + async def test_22_admin_list_members(self, client): + r = await client.get( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members", + headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # 23 – coach liste les membres → 200 + async def test_23_coach_list_members(self, client): + r = await client.get( + f"/groups/{TestGroupsScenario.coach_group_uuid}/members", + headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # 24 – user liste les groupes du coach (owner) → 200 + async def test_24_user_list_owner_groups(self, client): + r = await client.get( + f"/groups/owner/{TestGroupsScenario.coach_uuid}", + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 200 + groups = r.json() + assert any(g["id"] == TestGroupsScenario.coach_group_uuid for g in groups) + + # 25 – user quitte le groupe → 204 + async def test_25_user_leave_group_success(self, client): + r = await client.delete( + f"/groups/{TestGroupsScenario.coach_group_uuid}/leave", + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 204 + + # 26 – user quitte à nouveau → 404 + async def test_26_user_leave_group_not_found(self, client): + r = await client.delete( + f"/groups/{TestGroupsScenario.coach_group_uuid}/leave", + headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + ) + assert r.status_code == 404 + + +# créer un user/ créer un coach (user) +# admin update role coach add coach +# coach login for add new coach token +# coach créate a groups +# coach try to get one group wrong uuid +# coach try to get one group good uuid +# coach update group 200 +# user update group 403 +# admin update group 200 +# user wrong token get groups 401 +# user get groups 200 +# user create groups 403 +# admin create group 200 +# coach delte admin group 403 +# admin delete admin group 200 +# coach add user member to group 200 +# admin remove user member to group 200 +# admin add user member to coac group 200 +# user list member of group 403 +# admin list member of group 200 +# coach list member of group 200 +# user list owner groups (all coach group) 200 +# user leave groups 204 +# user leave groups not found 404 \ No newline at end of file diff --git a/src/tracknatrainapi.egg-info/PKG-INFO b/src/tracknatrainapi.egg-info/PKG-INFO new file mode 100644 index 0000000..d2711b8 --- /dev/null +++ b/src/tracknatrainapi.egg-info/PKG-INFO @@ -0,0 +1,52 @@ +Metadata-Version: 2.4 +Name: tracknatrainapi +Version: 0.4.0 +Requires-Python: >=3.13 +Requires-Dist: annotated-types==0.7.0 +Requires-Dist: anyio==4.9.0 +Requires-Dist: bcrypt==4.3.0 +Requires-Dist: boto3==1.37.37 +Requires-Dist: botocore==1.37.37 +Requires-Dist: cffi==1.17.1 +Requires-Dist: click==8.1.8 +Requires-Dist: cryptography==44.0.2 +Requires-Dist: dnspython==2.7.0 +Requires-Dist: ecdsa==0.19.1 +Requires-Dist: email-validator==2.2.0 +Requires-Dist: exceptiongroup==1.2.2 +Requires-Dist: fastapi==0.115.12 +Requires-Dist: greenlet==3.1.1 +Requires-Dist: h11==0.14.0 +Requires-Dist: idna==3.10 +Requires-Dist: jmespath==1.0.1 +Requires-Dist: passlib[bcrypt]>=1.7.4 +Requires-Dist: psycopg2-binary==2.9.10 +Requires-Dist: pyasn1==0.4.8 +Requires-Dist: pycparser==2.22 +Requires-Dist: pydantic==2.11.3 +Requires-Dist: pydantic-core==2.33.1 +Requires-Dist: python-dateutil==2.9.0.post0 +Requires-Dist: python-dotenv==1.1.0 +Requires-Dist: python-jose==3.4.0 +Requires-Dist: python-multipart==0.0.20 +Requires-Dist: rsa==4.9 +Requires-Dist: s3transfer==0.11.5 +Requires-Dist: six==1.17.0 +Requires-Dist: sniffio==1.3.1 +Requires-Dist: sqlalchemy==2.0.40 +Requires-Dist: starlette==0.46.2 +Requires-Dist: typing-extensions==4.13.2 +Requires-Dist: typing-inspection==0.4.0 +Requires-Dist: urllib3==2.4.0 +Requires-Dist: uvicorn==0.34.1 +Requires-Dist: pytest>=7.0 +Requires-Dist: pytest-asyncio>=0.20 +Requires-Dist: httpx>=0.24 +Requires-Dist: pytest-cov>=4.0 +Requires-Dist: coverage>=6.0 +Provides-Extra: testing +Requires-Dist: pytest>=7.0; extra == "testing" +Requires-Dist: pytest-asyncio>=0.20; extra == "testing" +Requires-Dist: httpx>=0.24; extra == "testing" +Requires-Dist: pytest-cov>=4.0; extra == "testing" +Requires-Dist: coverage>=6.0; extra == "testing" diff --git a/src/tracknatrainapi.egg-info/SOURCES.txt b/src/tracknatrainapi.egg-info/SOURCES.txt new file mode 100644 index 0000000..b933a86 --- /dev/null +++ b/src/tracknatrainapi.egg-info/SOURCES.txt @@ -0,0 +1,60 @@ +README.md +pyproject.toml +src/__init__.py +src/container.py +src/main.py +src/adapters/inmemory/repositories/diet.py +src/adapters/inmemory/repositories/exercise.py +src/adapters/inmemory/repositories/group.py +src/adapters/inmemory/repositories/profile.py +src/adapters/inmemory/repositories/training.py +src/adapters/sqlalchemy/db.py +src/adapters/sqlalchemy/models.py +src/adapters/sqlalchemy/repositories/diet.py +src/adapters/sqlalchemy/repositories/exercise.py +src/adapters/sqlalchemy/repositories/group.py +src/adapters/sqlalchemy/repositories/profile.py +src/adapters/sqlalchemy/repositories/training.py +src/domain/exceptions.py +src/domain/lib/jwt_manager.py +src/domain/lib/security.py +src/domain/model/diet.py +src/domain/model/exercise.py +src/domain/model/group.py +src/domain/model/profile.py +src/domain/model/training.py +src/domain/ports/diet_repository.py +src/domain/ports/exercise_repository.py +src/domain/ports/group_repository.py +src/domain/ports/password_hasher.py +src/domain/ports/profile_repository.py +src/domain/ports/training_repository.py +src/domain/services/diet.py +src/domain/services/exercise.py +src/domain/services/group.py +src/domain/services/profile.py +src/domain/services/training.py +src/entrypoints/__init__.py +src/entrypoints/api/__init__.py +src/entrypoints/api/deps/auth.py +src/entrypoints/api/deps/roles.py +src/entrypoints/api/routers/__init__.py +src/entrypoints/api/routers/diet.py +src/entrypoints/api/routers/exercise.py +src/entrypoints/api/routers/group.py +src/entrypoints/api/routers/profile.py +src/entrypoints/api/routers/training.py +src/entrypoints/api/schemas/__init__.py +src/entrypoints/api/schemas/diet.py +src/entrypoints/api/schemas/exercise.py +src/entrypoints/api/schemas/group.py +src/entrypoints/api/schemas/profile.py +src/entrypoints/api/schemas/training.py +src/entrypoints/api/tests/__init__.py +src/entrypoints/api/tests/conftest.py +src/entrypoints/api/tests/profiles.py +src/tracknatrainapi.egg-info/PKG-INFO +src/tracknatrainapi.egg-info/SOURCES.txt +src/tracknatrainapi.egg-info/dependency_links.txt +src/tracknatrainapi.egg-info/requires.txt +src/tracknatrainapi.egg-info/top_level.txt \ No newline at end of file diff --git a/src/tracknatrainapi.egg-info/dependency_links.txt b/src/tracknatrainapi.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/tracknatrainapi.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/tracknatrainapi.egg-info/requires.txt b/src/tracknatrainapi.egg-info/requires.txt new file mode 100644 index 0000000..abe612a --- /dev/null +++ b/src/tracknatrainapi.egg-info/requires.txt @@ -0,0 +1,49 @@ +annotated-types==0.7.0 +anyio==4.9.0 +bcrypt==4.3.0 +boto3==1.37.37 +botocore==1.37.37 +cffi==1.17.1 +click==8.1.8 +cryptography==44.0.2 +dnspython==2.7.0 +ecdsa==0.19.1 +email-validator==2.2.0 +exceptiongroup==1.2.2 +fastapi==0.115.12 +greenlet==3.1.1 +h11==0.14.0 +idna==3.10 +jmespath==1.0.1 +passlib[bcrypt]>=1.7.4 +psycopg2-binary==2.9.10 +pyasn1==0.4.8 +pycparser==2.22 +pydantic==2.11.3 +pydantic-core==2.33.1 +python-dateutil==2.9.0.post0 +python-dotenv==1.1.0 +python-jose==3.4.0 +python-multipart==0.0.20 +rsa==4.9 +s3transfer==0.11.5 +six==1.17.0 +sniffio==1.3.1 +sqlalchemy==2.0.40 +starlette==0.46.2 +typing-extensions==4.13.2 +typing-inspection==0.4.0 +urllib3==2.4.0 +uvicorn==0.34.1 +pytest>=7.0 +pytest-asyncio>=0.20 +httpx>=0.24 +pytest-cov>=4.0 +coverage>=6.0 + +[testing] +pytest>=7.0 +pytest-asyncio>=0.20 +httpx>=0.24 +pytest-cov>=4.0 +coverage>=6.0 diff --git a/src/tracknatrainapi.egg-info/top_level.txt b/src/tracknatrainapi.egg-info/top_level.txt new file mode 100644 index 0000000..980e8ac --- /dev/null +++ b/src/tracknatrainapi.egg-info/top_level.txt @@ -0,0 +1,6 @@ +__init__ +adapters +container +domain +entrypoints +main From 576ea8548c0f8dccdd94cfb0714b379b080a8600 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 10:20:08 +0200 Subject: [PATCH 16/28] remove info files... was a mistake --- .gitignore | 2 + src/tracknatrainapi.egg-info/PKG-INFO | 52 ---------------- src/tracknatrainapi.egg-info/SOURCES.txt | 60 ------------------- .../dependency_links.txt | 1 - src/tracknatrainapi.egg-info/requires.txt | 49 --------------- src/tracknatrainapi.egg-info/top_level.txt | 6 -- 6 files changed, 2 insertions(+), 168 deletions(-) delete mode 100644 src/tracknatrainapi.egg-info/PKG-INFO delete mode 100644 src/tracknatrainapi.egg-info/SOURCES.txt delete mode 100644 src/tracknatrainapi.egg-info/dependency_links.txt delete mode 100644 src/tracknatrainapi.egg-info/requires.txt delete mode 100644 src/tracknatrainapi.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 9320c18..6ab0bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # PYTHON __pycache__/ +# Info file +tracknatrainapi.egg-info/ \ No newline at end of file diff --git a/src/tracknatrainapi.egg-info/PKG-INFO b/src/tracknatrainapi.egg-info/PKG-INFO deleted file mode 100644 index d2711b8..0000000 --- a/src/tracknatrainapi.egg-info/PKG-INFO +++ /dev/null @@ -1,52 +0,0 @@ -Metadata-Version: 2.4 -Name: tracknatrainapi -Version: 0.4.0 -Requires-Python: >=3.13 -Requires-Dist: annotated-types==0.7.0 -Requires-Dist: anyio==4.9.0 -Requires-Dist: bcrypt==4.3.0 -Requires-Dist: boto3==1.37.37 -Requires-Dist: botocore==1.37.37 -Requires-Dist: cffi==1.17.1 -Requires-Dist: click==8.1.8 -Requires-Dist: cryptography==44.0.2 -Requires-Dist: dnspython==2.7.0 -Requires-Dist: ecdsa==0.19.1 -Requires-Dist: email-validator==2.2.0 -Requires-Dist: exceptiongroup==1.2.2 -Requires-Dist: fastapi==0.115.12 -Requires-Dist: greenlet==3.1.1 -Requires-Dist: h11==0.14.0 -Requires-Dist: idna==3.10 -Requires-Dist: jmespath==1.0.1 -Requires-Dist: passlib[bcrypt]>=1.7.4 -Requires-Dist: psycopg2-binary==2.9.10 -Requires-Dist: pyasn1==0.4.8 -Requires-Dist: pycparser==2.22 -Requires-Dist: pydantic==2.11.3 -Requires-Dist: pydantic-core==2.33.1 -Requires-Dist: python-dateutil==2.9.0.post0 -Requires-Dist: python-dotenv==1.1.0 -Requires-Dist: python-jose==3.4.0 -Requires-Dist: python-multipart==0.0.20 -Requires-Dist: rsa==4.9 -Requires-Dist: s3transfer==0.11.5 -Requires-Dist: six==1.17.0 -Requires-Dist: sniffio==1.3.1 -Requires-Dist: sqlalchemy==2.0.40 -Requires-Dist: starlette==0.46.2 -Requires-Dist: typing-extensions==4.13.2 -Requires-Dist: typing-inspection==0.4.0 -Requires-Dist: urllib3==2.4.0 -Requires-Dist: uvicorn==0.34.1 -Requires-Dist: pytest>=7.0 -Requires-Dist: pytest-asyncio>=0.20 -Requires-Dist: httpx>=0.24 -Requires-Dist: pytest-cov>=4.0 -Requires-Dist: coverage>=6.0 -Provides-Extra: testing -Requires-Dist: pytest>=7.0; extra == "testing" -Requires-Dist: pytest-asyncio>=0.20; extra == "testing" -Requires-Dist: httpx>=0.24; extra == "testing" -Requires-Dist: pytest-cov>=4.0; extra == "testing" -Requires-Dist: coverage>=6.0; extra == "testing" diff --git a/src/tracknatrainapi.egg-info/SOURCES.txt b/src/tracknatrainapi.egg-info/SOURCES.txt deleted file mode 100644 index b933a86..0000000 --- a/src/tracknatrainapi.egg-info/SOURCES.txt +++ /dev/null @@ -1,60 +0,0 @@ -README.md -pyproject.toml -src/__init__.py -src/container.py -src/main.py -src/adapters/inmemory/repositories/diet.py -src/adapters/inmemory/repositories/exercise.py -src/adapters/inmemory/repositories/group.py -src/adapters/inmemory/repositories/profile.py -src/adapters/inmemory/repositories/training.py -src/adapters/sqlalchemy/db.py -src/adapters/sqlalchemy/models.py -src/adapters/sqlalchemy/repositories/diet.py -src/adapters/sqlalchemy/repositories/exercise.py -src/adapters/sqlalchemy/repositories/group.py -src/adapters/sqlalchemy/repositories/profile.py -src/adapters/sqlalchemy/repositories/training.py -src/domain/exceptions.py -src/domain/lib/jwt_manager.py -src/domain/lib/security.py -src/domain/model/diet.py -src/domain/model/exercise.py -src/domain/model/group.py -src/domain/model/profile.py -src/domain/model/training.py -src/domain/ports/diet_repository.py -src/domain/ports/exercise_repository.py -src/domain/ports/group_repository.py -src/domain/ports/password_hasher.py -src/domain/ports/profile_repository.py -src/domain/ports/training_repository.py -src/domain/services/diet.py -src/domain/services/exercise.py -src/domain/services/group.py -src/domain/services/profile.py -src/domain/services/training.py -src/entrypoints/__init__.py -src/entrypoints/api/__init__.py -src/entrypoints/api/deps/auth.py -src/entrypoints/api/deps/roles.py -src/entrypoints/api/routers/__init__.py -src/entrypoints/api/routers/diet.py -src/entrypoints/api/routers/exercise.py -src/entrypoints/api/routers/group.py -src/entrypoints/api/routers/profile.py -src/entrypoints/api/routers/training.py -src/entrypoints/api/schemas/__init__.py -src/entrypoints/api/schemas/diet.py -src/entrypoints/api/schemas/exercise.py -src/entrypoints/api/schemas/group.py -src/entrypoints/api/schemas/profile.py -src/entrypoints/api/schemas/training.py -src/entrypoints/api/tests/__init__.py -src/entrypoints/api/tests/conftest.py -src/entrypoints/api/tests/profiles.py -src/tracknatrainapi.egg-info/PKG-INFO -src/tracknatrainapi.egg-info/SOURCES.txt -src/tracknatrainapi.egg-info/dependency_links.txt -src/tracknatrainapi.egg-info/requires.txt -src/tracknatrainapi.egg-info/top_level.txt \ No newline at end of file diff --git a/src/tracknatrainapi.egg-info/dependency_links.txt b/src/tracknatrainapi.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/src/tracknatrainapi.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/tracknatrainapi.egg-info/requires.txt b/src/tracknatrainapi.egg-info/requires.txt deleted file mode 100644 index abe612a..0000000 --- a/src/tracknatrainapi.egg-info/requires.txt +++ /dev/null @@ -1,49 +0,0 @@ -annotated-types==0.7.0 -anyio==4.9.0 -bcrypt==4.3.0 -boto3==1.37.37 -botocore==1.37.37 -cffi==1.17.1 -click==8.1.8 -cryptography==44.0.2 -dnspython==2.7.0 -ecdsa==0.19.1 -email-validator==2.2.0 -exceptiongroup==1.2.2 -fastapi==0.115.12 -greenlet==3.1.1 -h11==0.14.0 -idna==3.10 -jmespath==1.0.1 -passlib[bcrypt]>=1.7.4 -psycopg2-binary==2.9.10 -pyasn1==0.4.8 -pycparser==2.22 -pydantic==2.11.3 -pydantic-core==2.33.1 -python-dateutil==2.9.0.post0 -python-dotenv==1.1.0 -python-jose==3.4.0 -python-multipart==0.0.20 -rsa==4.9 -s3transfer==0.11.5 -six==1.17.0 -sniffio==1.3.1 -sqlalchemy==2.0.40 -starlette==0.46.2 -typing-extensions==4.13.2 -typing-inspection==0.4.0 -urllib3==2.4.0 -uvicorn==0.34.1 -pytest>=7.0 -pytest-asyncio>=0.20 -httpx>=0.24 -pytest-cov>=4.0 -coverage>=6.0 - -[testing] -pytest>=7.0 -pytest-asyncio>=0.20 -httpx>=0.24 -pytest-cov>=4.0 -coverage>=6.0 diff --git a/src/tracknatrainapi.egg-info/top_level.txt b/src/tracknatrainapi.egg-info/top_level.txt deleted file mode 100644 index 980e8ac..0000000 --- a/src/tracknatrainapi.egg-info/top_level.txt +++ /dev/null @@ -1,6 +0,0 @@ -__init__ -adapters -container -domain -entrypoints -main From 3ddb34169c3db71676d3054721aaa0ad05a61e5e Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:00:22 +0200 Subject: [PATCH 17/28] remove s in profile files --- src/entrypoints/api/tests/{profiles.py => profile.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/entrypoints/api/tests/{profiles.py => profile.py} (100%) diff --git a/src/entrypoints/api/tests/profiles.py b/src/entrypoints/api/tests/profile.py similarity index 100% rename from src/entrypoints/api/tests/profiles.py rename to src/entrypoints/api/tests/profile.py From e94c35b7ac9e6e700d652ea485cad9687e0dccfc Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:02:25 +0200 Subject: [PATCH 18/28] fix in router group form user leave groups convert is uuid (as string) to a uuid : UUID for the service --- src/entrypoints/api/routers/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entrypoints/api/routers/group.py b/src/entrypoints/api/routers/group.py index b76f273..cc8de3f 100644 --- a/src/entrypoints/api/routers/group.py +++ b/src/entrypoints/api/routers/group.py @@ -100,7 +100,7 @@ def list_owner_groups(owner_id: UUID): def leave_group(group_id: UUID, user=Depends(get_current_user)): service = container.get_group_service() try: - service.remove_member(group_id, user["sub"]) + service.remove_member(group_id, UUID(user["sub"])) except NotFoundError as e: raise HTTPException(404, str(e)) From 994842616f13e50b1ff22929dbeb594b7ed3ad56 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:02:46 +0200 Subject: [PATCH 19/28] fix endpoint remove "/" for create and get all exercices cause no need --- src/entrypoints/api/routers/exercise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/entrypoints/api/routers/exercise.py b/src/entrypoints/api/routers/exercise.py index 875ab75..c4c8f13 100644 --- a/src/entrypoints/api/routers/exercise.py +++ b/src/entrypoints/api/routers/exercise.py @@ -12,7 +12,7 @@ router = APIRouter(prefix="/exercises", tags=["exercises"]) -@router.post("/", response_model=ExerciseRead, status_code=201, dependencies=[Depends(require_roles("admin", "coach"))]) +@router.post("", response_model=ExerciseRead, status_code=201, dependencies=[Depends(require_roles("admin", "coach"))]) def create_exercise( dto: ExerciseCreate, user=Depends(get_current_user) @@ -30,7 +30,7 @@ def create_exercise( return ExerciseRead.model_validate(exercise) -@router.get("/", response_model=List[ExerciseRead], dependencies=[Depends(get_current_user)]) +@router.get("", response_model=List[ExerciseRead], dependencies=[Depends(get_current_user)]) def get_exercises( user=Depends(get_current_user) ): From 7dd53e4df87e03dbdcabae8bcf470bec45620d1a Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:03:20 +0200 Subject: [PATCH 20/28] update conftest for using fixture score session 'test_state' --- src/entrypoints/api/tests/conftest.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/entrypoints/api/tests/conftest.py b/src/entrypoints/api/tests/conftest.py index ddd1787..100c352 100644 --- a/src/entrypoints/api/tests/conftest.py +++ b/src/entrypoints/api/tests/conftest.py @@ -7,35 +7,30 @@ -# Avant tout, on définit la variable d'environnement @pytest.fixture(scope="session", autouse=True) def set_test_env(): - os.environ["ENV"] = "test" # Configure l'environnement à "test" + os.environ["ENV"] = "test" yield - # Optionnel : tu peux aussi réinitialiser la variable d'environnement après les tests si nécessaire -# Fixture pour initialiser un conteneur unique pour tous les tests @pytest.fixture(scope="module", autouse=True) def container(): - # Créer un conteneur en mémoire avec ENV='test' - c = Container(env="test") # Assure que le conteneur utilise l'environnement 'test' + c = Container(env="test") - # Assurer que le conteneur utilise une base en mémoire assert os.getenv("ENV") == "test", "L'environnement n'est pas correctement configuré sur 'test'." - # Retourner le conteneur pour qu'il soit partagé dans tous les tests return c -# Fournit un client HTTPX asynchrone monté sur l'ASGI de FastAPI @pytest_asyncio.fixture async def client(container): - """ - Fournit un AsyncClient HTTPX monté sur l'ASGI FastAPI avec un environnement de test. - """ + transport = ASGITransport(app=app) - # Créer et retourner un client HTTPX pour les tests async with AsyncClient(transport=transport, base_url="http://testserver") as ac: - yield ac # Retourne le client pour les tests + yield ac + +@pytest.fixture(scope="session") +def test_state(): + + return {} \ No newline at end of file From b94848af9505f9e64de1f673076acb91a902a204 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:03:59 +0200 Subject: [PATCH 21/28] create two more test for delete groups, and using the fixture from conftest for share variables like token profil_id etc --- src/entrypoints/api/tests/group.py | 180 ++++++++++++++++------------- 1 file changed, 98 insertions(+), 82 deletions(-) diff --git a/src/entrypoints/api/tests/group.py b/src/entrypoints/api/tests/group.py index b9d3d3c..7a0a2eb 100644 --- a/src/entrypoints/api/tests/group.py +++ b/src/entrypoints/api/tests/group.py @@ -4,16 +4,9 @@ @pytest.mark.asyncio class TestGroupsScenario: - admin_token: str = "" - user_token: str = "" - coach_token: str = "" - user_uuid: str = "" - coach_uuid: str = "" - coach_group_uuid: str = "" - admin_group_uuid: str = "" # 01 – création d’un user - async def test_01_create_user_profile(self, client): + async def test_01_create_user_profile(self, client, test_state): payload = { "email": "alice@example.com", "password": "Secret123!", @@ -25,11 +18,11 @@ async def test_01_create_user_profile(self, client): r = await client.post("/profiles", json=payload) assert r.status_code == 201 body = r.json() - TestGroupsScenario.user_token = body["token"]["access_token"] - TestGroupsScenario.user_uuid = body["profile"]["id"] + test_state["user_token"] = body["token"]["access_token"] + test_state["user_uuid"] = body["profile"]["id"] # 02 – création d’un coach (initialement role=["user"]) - async def test_02_create_coach_profile(self, client): + async def test_02_create_coach_profile(self, client, test_state): payload = { "email": "coach@example.com", "password": "CoachPass123!", @@ -41,32 +34,32 @@ async def test_02_create_coach_profile(self, client): r = await client.post("/profiles", json=payload) assert r.status_code == 201 body = r.json() - TestGroupsScenario.coach_token = body["token"]["access_token"] - TestGroupsScenario.coach_uuid = body["profile"]["id"] + test_state["coach_token"] = body["token"]["access_token"] + test_state["coach_uuid"] = body["profile"]["id"] # 03 – login admin - async def test_03_admin_login(self, client): + async def test_03_admin_login(self, client, test_state): r = await client.post( "/profiles/login", json={"email": "admin@mail.fr", "password": "123456789"} ) assert r.status_code == 200 - TestGroupsScenario.admin_token = r.json()["access_token"] + test_state["admin_token"] = r.json()["access_token"] # 04 – promotion du coach → roles=["user","coach"] - async def test_04_admin_promote_coach(self, client): + async def test_04_admin_promote_coach(self, client, test_state): update_data = {"roles": ["user", "coach"]} r = await client.patch( - f"/profiles/{TestGroupsScenario.coach_uuid}/roles", + f"/profiles/{test_state['coach_uuid']}/roles", json=update_data, - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 200 roles = r.json()["roles"] assert "coach" in roles # 05 – relogin coach pour avoir le nouveau token - async def test_05_coach_login_success(self, client): + async def test_05_coach_login_success(self, client, test_state): r = await client.post( "/profiles/login", json={"email": "coach@example.com", "password": "CoachPass123!"} @@ -74,74 +67,74 @@ async def test_05_coach_login_success(self, client): assert r.status_code == 200 data = r.json() assert "access_token" in data - TestGroupsScenario.coach_token = data["access_token"] + test_state["coach_token"] = data["access_token"] # 06 – coach crée un groupe - async def test_06_coach_create_group(self, client): + async def test_06_coach_create_group(self, client, test_state): payload = {"name": "CoachGroup", "description": "Groupe du coach"} r = await client.post( "/groups", json=payload, - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 201 grp = r.json() - TestGroupsScenario.coach_group_uuid = grp["id"] + test_state["coach_group_uuid"] = grp["id"] assert grp["name"] == "CoachGroup" # 07 – coach lit un groupe inexistant → 404 - async def test_07_coach_get_group_not_found(self, client): + async def test_07_coach_get_group_not_found(self, client, test_state): fake = str(uuid.UUID(int=0)) r = await client.get( f"/groups/{fake}", - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 404 # 08 – coach lit son groupe → 200 - async def test_08_coach_get_group_success(self, client): + async def test_08_coach_get_group_success(self, client, test_state): r = await client.get( - f"/groups/{TestGroupsScenario.coach_group_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + f"/groups/{test_state['coach_group_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 200 - assert r.json()["id"] == TestGroupsScenario.coach_group_uuid + assert r.json()["id"] == test_state["coach_group_uuid"] # 09 – coach met à jour son groupe → 200 - async def test_09_coach_update_group(self, client): + async def test_09_coach_update_group(self, client, test_state): update_data = {"name": "CoachGroupNew", "description": "Desc maj"} r = await client.patch( - f"/groups/{TestGroupsScenario.coach_group_uuid}", + f"/groups/{test_state['coach_group_uuid']}", json=update_data, - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 200 body = r.json() assert body["name"] == "CoachGroupNew" # 10 – user tente de mettre à jour le groupe du coach → 403 - async def test_10_user_update_group_forbidden(self, client): + async def test_10_user_update_group_forbidden(self, client, test_state): update_data = {"name": "Hack"} r = await client.patch( - f"/groups/{TestGroupsScenario.coach_group_uuid}", + f"/groups/{test_state['coach_group_uuid']}", json=update_data, - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 403 # 11 – admin met à jour le groupe du coach → 200 - async def test_11_admin_update_group(self, client): + async def test_11_admin_update_group(self, client, test_state): update_data = {"description": "Admin update"} r = await client.patch( - f"/groups/{TestGroupsScenario.coach_group_uuid}", + f"/groups/{test_state['coach_group_uuid']}", json=update_data, - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 200 assert r.json()["description"] == "Admin update" # 12 – user avec token invalide lit la liste → 401 - async def test_12_user_get_groups_invalid_token(self, client): + async def test_12_user_get_groups_invalid_token(self, client, test_state): r = await client.get( "/groups", headers={"Authorization": "Bearer invalidtoken"} @@ -149,129 +142,147 @@ async def test_12_user_get_groups_invalid_token(self, client): assert r.status_code == 401 # 13 – user lit tous les groupes → 200 - async def test_13_user_list_groups(self, client): + async def test_13_user_list_groups(self, client, test_state): r = await client.get( "/groups", - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 200 assert isinstance(r.json(), list) # 14 – user tente de créer un groupe → 403 - async def test_14_user_create_group_forbidden(self, client): + async def test_14_user_create_group_forbidden(self, client, test_state): payload = {"name": "UserGroup", "description": "Desc"} r = await client.post( "/groups", json=payload, - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 403 # 15 – admin crée un groupe - async def test_15_admin_create_group(self, client): + async def test_15_admin_create_group(self, client, test_state): payload = {"name": "AdminGroup", "description": "Groupe admin"} r = await client.post( "/groups", json=payload, - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 201 grp = r.json() - TestGroupsScenario.admin_group_uuid = grp["id"] + test_state['admin_group_uuid'] = grp["id"] # 16 – coach supprime le groupe admin → 403 - async def test_16_coach_delete_admin_group_forbidden(self, client): + async def test_16_coach_delete_admin_group_forbidden(self, client, test_state): r = await client.delete( - f"/groups/{TestGroupsScenario.admin_group_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + f"/groups/{test_state['admin_group_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 403 # 17 – admin supprime son propre groupe → 204 - async def test_17_admin_delete_admin_group_success(self, client): + async def test_17_admin_delete_admin_group_success(self, client, test_state): r = await client.delete( - f"/groups/{TestGroupsScenario.admin_group_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + f"/groups/{test_state['admin_group_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 204 # 18 – coach ajoute l’utilisateur au groupe → 204 - async def test_18_coach_add_user_to_group(self, client): + async def test_18_coach_add_user_to_group(self, client, test_state): r = await client.post( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + f"/groups/{test_state['coach_group_uuid']}/members/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) assert r.status_code == 204 # 19 – admin retire l’utilisateur du groupe → 204 - async def test_19_admin_remove_user_from_group(self, client): + async def test_19_admin_remove_user_from_group(self, client, test_state): r = await client.delete( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + f"/groups/{test_state['coach_group_uuid']}/members/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 204, f"Failed to add member: { r.text }" # 20 – admin ré-ajoute l’utilisateur → 204 - async def test_20_admin_add_user_to_group(self, client): + async def test_20_admin_add_user_to_group(self, client, test_state): r = await client.post( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members/{TestGroupsScenario.user_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + f"/groups/{test_state['coach_group_uuid']}/members/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 204 # 21 – user tente de lister les membres → 403 - async def test_21_user_list_members_forbidden(self, client): + async def test_21_user_list_members_forbidden(self, client, test_state): r = await client.get( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members", - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + f"/groups/{test_state['coach_group_uuid']}/members", + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 403 # 22 – admin liste les membres → 200 - async def test_22_admin_list_members(self, client): + async def test_22_admin_list_members(self, client, test_state): r = await client.get( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members", - headers={"Authorization": f"Bearer {TestGroupsScenario.admin_token}"} + f"/groups/{test_state['coach_group_uuid']}/members", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} ) assert r.status_code == 200 assert isinstance(r.json(), list) # 23 – coach liste les membres → 200 - async def test_23_coach_list_members(self, client): + async def test_23_coach_list_members(self, client, test_state): r = await client.get( - f"/groups/{TestGroupsScenario.coach_group_uuid}/members", - headers={"Authorization": f"Bearer {TestGroupsScenario.coach_token}"} + f"/groups/{test_state['coach_group_uuid']}/members", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} ) + print(r.text, 'le user dans le groups du coach') assert r.status_code == 200 assert isinstance(r.json(), list) # 24 – user liste les groupes du coach (owner) → 200 - async def test_24_user_list_owner_groups(self, client): + async def test_24_user_list_owner_groups(self, client, test_state): r = await client.get( - f"/groups/owner/{TestGroupsScenario.coach_uuid}", - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + f"/groups/owner/{test_state['coach_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 200 groups = r.json() - assert any(g["id"] == TestGroupsScenario.coach_group_uuid for g in groups) + assert any(g["id"] == test_state['coach_group_uuid'] for g in groups) + # 25 – user quitte le groupe → 204 - async def test_25_user_leave_group_success(self, client): + async def test_25_user_leave_group_success(self, client, test_state): r = await client.delete( - f"/groups/{TestGroupsScenario.coach_group_uuid}/leave", - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + f"/groups/{test_state['coach_group_uuid']}/leave", + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) - assert r.status_code == 204 + assert r.status_code == 204, f"Failed to leave group: { r.text }" + # 26 – user quitte à nouveau → 404 - async def test_26_user_leave_group_not_found(self, client): + async def test_26_user_leave_group_not_found(self, client, test_state): r = await client.delete( - f"/groups/{TestGroupsScenario.coach_group_uuid}/leave", - headers={"Authorization": f"Bearer {TestGroupsScenario.user_token}"} + f"/groups/{test_state['coach_group_uuid']}/leave", + headers={"Authorization": f"Bearer {test_state['user_token']}"} ) assert r.status_code == 404 + async def test_27_user_delete_group_forbidden(self, client, test_state): + r = await client.delete( + f"/groups/{test_state['coach_group_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + + async def test_28_coach_delete_own_group(self, client, test_state): + r = await client.delete( + f"/groups/{test_state['coach_group_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 204 + # créer un user/ créer un coach (user) # admin update role coach add coach @@ -296,4 +307,9 @@ async def test_26_user_leave_group_not_found(self, client): # coach list member of group 200 # user list owner groups (all coach group) 200 # user leave groups 204 -# user leave groups not found 404 \ No newline at end of file +# user leave groups not found 404 +# user try to delete group 403 +# coach try to delete admin group 403 +# admin delete admin group 204 +# coach delete own group 204 + From 13b18aab7aacccde44f8a541af64e50c1c376a8f Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Thu, 24 Jul 2025 16:04:30 +0200 Subject: [PATCH 22/28] create exercices test using test_state fixture to have tokend and uid from groups test --- src/entrypoints/api/tests/exercise.py | 185 ++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/entrypoints/api/tests/exercise.py diff --git a/src/entrypoints/api/tests/exercise.py b/src/entrypoints/api/tests/exercise.py new file mode 100644 index 0000000..dc2d63f --- /dev/null +++ b/src/entrypoints/api/tests/exercise.py @@ -0,0 +1,185 @@ +import pytest + + +@pytest.mark.asyncio +class TestExercisesScenario: + + # 05 – user tente de créer un exercice → 403 + async def test_01_user_create_exercise_forbidden(self, client, test_state): + payload = {"name": "Exo1", "description": "desc exo1"} + r = await client.post( + "/exercises", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # 06 – coach crée un exercice → 201 + async def test_02_coach_create_exercise(self, client, test_state): + payload = {"name": "ExoCoach", "description": "desc coach"} + r = await client.post( + "/exercises", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['coach_exercise_id'] = body["id"] + + # 07 – admin crée un exercice → 201 + async def test_03_admin_create_exercise(self, client, test_state): + payload = {"name": "ExoAdmin", "description": "desc admin"} + r = await client.post( + "/exercises", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['exercise_id'] = body["id"] + + # 08 – user tente de modifier exercice → 403 + async def test_04_user_update_exercise_forbidden(self, client, test_state): + payload = {"name": "Hacked", "description": "Hacked desc"} + r = await client.patch( + f"/exercises/{test_state['coach_exercise_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # 09 – admin donne rôle coach à user, user tente update → 403; admin retire coach à user + async def test_05_admin_update_roles_user_and_try_update_exercise(self, client, test_state): + # Ajoute rôle coach à user + r = await client.patch( + f"/profiles/{test_state['user_uuid']}/roles", + json={"roles": ["user", "coach"]}, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + # User tente update (il n'est pas owner) + payload = {"name": "Hacked2", "description": "Hacked desc2"} + r = await client.patch( + f"/exercises/{test_state['coach_exercise_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + # Admin retire coach à user + r = await client.patch( + f"/profiles/{test_state['user_uuid']}/roles", + json={"roles": ["user"]}, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # 10 – admin update exercice → 200 + async def test_06_admin_update_exercise(self, client, test_state): + payload = {"name": "AdminUpdate", "description": "Admin modif"} + r = await client.patch( + f"/exercises/{test_state['coach_exercise_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # 11 – coach update exercice → 200 + async def test_07_coach_update_exercise(self, client, test_state): + payload = {"name": "CoachUpdate", "description": "Coach modif"} + r = await client.patch( + f"/exercises/{test_state['coach_exercise_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + + # 12 – user get exercises → 200 + async def test_08_user_get_exercises(self, client, test_state): + r = await client.get( + "/exercises", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # 13 – user get one exercise → 200 + async def test_09_user_get_one_exercise(self, client, test_state): + r = await client.get( + f"/exercises/{test_state['coach_exercise_id']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + + # 14 – user get all mine exercises → 403 + async def test_10_user_get_mine_exercises_forbidden(self, client, test_state): + r = await client.get( + "/exercises/mine", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # 15 – coach get all mine exercises → 200 + async def test_11_coach_get_mine_exercises(self, client, test_state): + r = await client.get( + "/exercises/mine", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # 16 – admin supprime exercice coach → 204 + async def test_12_admin_delete_coach_exercise(self, client, test_state): + r = await client.delete( + f"/exercises/{test_state['coach_exercise_id']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 204 + + # 17 – coach crée un exercice → 201 + async def test_13_coach_create_exercise(self, client, test_state): + payload = {"name": "ExoCoach2", "description": "desc coach 2"} + r = await client.post( + "/exercises", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['coach_exercise_id'] = body["id"] + + # 18 – user delete coach exercise → 403 + async def test_14_user_delete_coach_exercise_forbidden(self, client, test_state): + r = await client.delete( + f"/exercises/{test_state['coach_exercise_id']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # 19 – coach delete exercise → 204 + async def test_15_coach_delete_exercise(self, client, test_state): + r = await client.delete( + f"/exercises/{test_state['coach_exercise_id']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 204 + + + +# create new user profile +# create new coach profile +# admin update roles for coach to coach +# user try to create a exercise 403 +# coach create exercise 201 +# admin creatre exercise 201 +# user try to update exercise 403 +# admin update user to coach and user try to update exercise 403, admin remove coach role from user +# admin update exercise 200 +# coach update exercise 200 +# user get exercises 200 +# user get one exercise 200 +# user get all mine exercises 403 +# coach get all mine exercises 200 +# admin delete coach exercise 204 +# coach create exercise 201 +# user delete coach exercise 403 +# coach delete exercise 204 \ No newline at end of file From 0b8b6b91d60b9573ad94bc3e6b4ac7c6be2eaadb Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 15:54:41 +0200 Subject: [PATCH 23/28] create training test --- src/entrypoints/api/tests/training.py | 331 ++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 src/entrypoints/api/tests/training.py diff --git a/src/entrypoints/api/tests/training.py b/src/entrypoints/api/tests/training.py new file mode 100644 index 0000000..f11e401 --- /dev/null +++ b/src/entrypoints/api/tests/training.py @@ -0,0 +1,331 @@ +# coach create an group → 201 +# coach create training for user -> 403 +# coach add user to group → 200 +# coach create training for user -> 201 +# user create trainging for user -> 403 +# coach update training for user -> 200 +# admin update training for user -> 200 +# user create tasks for training -> 403 +# coach create tasks for training -> 201 +# admin create tasks for training -> 201 +# coach get all trainings for user -> 200 +# admin get all trainings for user -> 200 +# user get all trainings for user -> 403 +# user get all mine trainings -> 200 +# user get all mine tasks -> 200 +# user update task -> 403 +# admin update task -> 200 +# coach update task -> 200 +# create new user (user2) -> 200 +# user create validation for task -> 200 +# user2 create validation for task -> 403 +# user get all validations for task -> 200 +# coach get all validations for task -> 200 +# admin get all validations for task -> 200 +# user2 get all validations for task -> 403 +# user get all validations for training -> 200 +# coach get all validations for training -> 200 +# admin get all validations for training -> 200 +# user2 get all validations for training -> 403 +# user delete validation for task -> 200 +# coach delete validation for task -> 403 +# admin delete validation for task -> 200 + + +import pytest + +@pytest.mark.asyncio +class TestTrainingScenario: + +# coach create an group → 201 + async def test_01_coach_create_group(self, client, test_state): + payload = {"name": "Group1", "description": "Test Group"} + r = await client.post( + "/groups", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['group_id'] = body["id"] +# coach create training for user -> 403 + async def test_02_coach_create_training_for_user_forbidden(self, client, test_state): + payload = {"name": "Training1", "description": "Test Training"} + r = await client.post( + f"/trainings/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 403 +# coach add user to group → 204 + async def test_03_coach_add_user_to_group(self, client, test_state): + r = await client.post( + f"/groups/{test_state['group_id']}/members/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 204 +# coach create training for user -> 201 + async def test_04_coach_create_training_for_user(self, client, test_state): + payload = {"name": "Training1", "description": "Test Training"} + r = await client.post( + f"/trainings/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['training_id'] = body["id"] +# user create trainging for user -> 403 + async def test_05_user_create_training_for_user_forbidden(self, client, test_state): + payload = {"name": "Training1", "description": "Test Training"} + r = await client.post( + f"/trainings/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 +# coach update training for user -> 200 + async def test_06_coach_update_training_for_user(self, client, test_state): + payload = {"name": "Training1 Updated", "description": "Test Training Updated"} + r = await client.patch( + f"/trainings/{test_state['training_id']}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 +# admin update training for user -> 200 + async def test_07_admin_update_training_for_user(self, client, test_state): + payload = {"name": "Training1 Updated", "description": "Test Training Updated"} + r = await client.patch( + f"/trainings/{test_state['training_id']}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 +# user create tasks for training -> 403 + async def test_08_user_create_tasks_for_training_forbidden(self, client, test_state): + payload = { + "exercise_name": "Squat", + "rest_time": 60, + "repetitions": 10, + "set_number": 3, + "method": "classic", + "rir": 2, + } + r = await client.post( + f"/trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 +# coach create tasks for training -> 201 + async def test_09_coach_create_tasks_for_training(self, client, test_state): + payload = { + "exercise_name": "Squat", + "rest_time": 60, + "repetitions": 10, + "set_number": 3, + "method": "classic", + "rir": 2, + } + r = await client.post( + f"/trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 +# admin create tasks for training -> 201 + async def test_10_admin_create_tasks_for_training(self, client, test_state): + payload = { + "exercise_name": "Squat", + "rest_time": 60, + "repetitions": 10, + "set_number": 3, + "method": "classic", + "rir": 2, + } + r = await client.post( + f"/trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['task_id'] = body["id"] +# coach get all trainings for user -> 200 + async def test_11_coach_get_all_trainings_for_user(self, client, test_state): + r = await client.get( + f"/trainings/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# admin get all trainings for user -> 200 + async def test_12_admin_get_all_trainings_for_user(self, client, test_state): + r = await client.get( + f"/trainings/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# user get all trainings for user -> 403 + async def test_13_user_get_all_trainings_for_user_forbidden(self, client, test_state): + r = await client.get( + f"/trainings/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 +# user get all mine trainings -> 200 + async def test_14_user_get_all_mine_trainings(self, client, test_state): + r = await client.get( + "/trainings/mine", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + +# user update task -> 403 + async def test_16_user_update_task_forbidden(self, client, test_state): + payload = {"name": "Task1 Updated", "description": "Test Task Updated"} + r = await client.patch( + f"trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks/{test_state['task_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 +# admin update task -> 200 + async def test_17_admin_update_task(self, client, test_state): + payload = {"name": "Task1 Updated", "description": "Test Task Updated"} + r = await client.patch( + f"trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks/{test_state['task_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 +# coach update task -> 200 + async def test_18_coach_update_task(self, client, test_state): + payload = {"name": "Task1 Updated", "description": "Test Task Updated"} + r = await client.patch( + f"trainings/{test_state['training_id']}/user/{test_state['user_uuid']}/tasks/{test_state['task_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 +# create new user (user2) -> 200 + async def test_19_create_new_user(self, client, test_state): + payload = { + "email": "bob@example.com", + "password": "Secret123!", + "confirm_password": "Secret123!"} + r = await client.post( + "/profiles", + json=payload, + ) + assert r.status_code == 201 + body = r.json() + test_state['user2_token'] = body["token"]["access_token"] + test_state['user2_uuid'] = body["profile"]["id"] +# user create validation for task -> 200 + async def test_20_user_create_validation_for_task(self, client, test_state): + payload = {"comment": "Good job!", "score": 5} + r = await client.post( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['validation_uuid'] = body["id"] +# user2 create validation for task -> 403 + async def test_21_user2_create_validation_for_task_forbidden(self, client, test_state): + payload = {"comment": "Good job!", "score": 5} + r = await client.post( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + json=payload, + headers={"Authorization": f"Bearer {test_state['user2_token']}"} + ) + assert r.status_code == 403 + +# user get all validations for task -> 200 + async def test_22_user_get_all_validations_for_task(self, client, test_state): + r = await client.get( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# coach get all validations for task -> 200 + async def test_23_coach_get_all_validations_for_task(self, client, test_state): + r = await client.get( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# admin get all validations for task -> 200 + async def test_24_admin_get_all_validations_for_task(self, client, test_state): + r = await client.get( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# user2 get all validations for task -> 403 + async def test_25_user2_get_all_validations_for_task_forbidden(self, client, test_state): + r = await client.get( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations", + headers={"Authorization": f"Bearer {test_state['user2_token']}"} + ) + assert r.status_code == 403 +# user get all validations for training -> 200 + async def test_26_user_get_all_validations_for_training(self, client, test_state): + r = await client.get( + f"trainings/{test_state['training_id']}/validations", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# coach get all validations for training -> 200 + async def test_27_coach_get_all_validations_for_training(self, client, test_state): + r = await client.get( + f"/trainings/{test_state['training_id']}/validations", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# admin get all validations for training -> 200 + async def test_28_admin_get_all_validations_for_training(self, client, test_state): + r = await client.get( + f"/trainings/{test_state['training_id']}/validations", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) +# user2 get all validations for training -> 403 + async def test_29_user2_get_all_validations_for_training_forbidden(self, client, test_state): + r = await client.get( + f"/trainings/{test_state['training_id']}/validations", + headers={"Authorization": f"Bearer {test_state['user2_token']}"} + ) + assert r.status_code == 403 +# user delete validation for task -> 204 + async def test_30_user_delete_validation_for_task(self, client, test_state): + r = await client.delete( + f"trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations/{test_state['validation_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 204 +# coach delete validation for task -> 403 + async def test_31_coach_delete_validation_for_task_forbidden(self, client, test_state): + r = await client.delete( + f"/trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations/{test_state['validation_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 403 +# admin delete validation for task -> 404 + async def test_32_admin_delete_validation_for_task_not_found(self, client, test_state): + r = await client.delete( + f"/trainings/{test_state['training_id']}/tasks/{test_state['task_id']}/validations/{test_state['validation_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 404 From 9bf6a63f7e48fb6e2ea48b0ad22138e099172797 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 16:37:48 +0200 Subject: [PATCH 24/28] fix diet service when update mealplan --- src/domain/services/diet.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/domain/services/diet.py b/src/domain/services/diet.py index 37ecd32..aa3a663 100644 --- a/src/domain/services/diet.py +++ b/src/domain/services/diet.py @@ -158,14 +158,16 @@ def update_meal_plan( ) -> DomainMealPlan: mp = self.get_meal_plan_by_id(plan_id) - domain_meals = [DomainMealItem(**m.model_dump()) for m in meals] - if not domain_meals: + updated_name = name if name is not None else mp.name + updated_meals = [DomainMealItem(**m.model_dump()) for m in meals] if meals is not None else mp.meals + + if not updated_meals: raise ValueError("Meal Plan must have at least one meal") if name is not None: - mp.name = name + mp.name = updated_name if meals is not None: - mp.meals = domain_meals + mp.meals = updated_meals return self._repo.update_meal_plan(mp) def delete_meal_plan(self, plan_id: UUID) -> None: From be9fd0866d69d5a1d8defef74ba2926e9ae114dd Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 16:37:58 +0200 Subject: [PATCH 25/28] create diet test scenario --- src/entrypoints/api/tests/diet.py | 510 ++++++++++++++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 src/entrypoints/api/tests/diet.py diff --git a/src/entrypoints/api/tests/diet.py b/src/entrypoints/api/tests/diet.py new file mode 100644 index 0000000..2bc1ebf --- /dev/null +++ b/src/entrypoints/api/tests/diet.py @@ -0,0 +1,510 @@ +import pytest + +@pytest.mark.asyncio +class TestDietScenario: + + # create new user (user3) not in team + async def test_01_create_new_user_not_in_team(self, client, test_state): + payload = { + "email": "user3@example.com", + "password": "Secret123!", + "confirm_password": "Secret123!" + } + r = await client.post( + "/profiles", + json=payload, + ) + assert r.status_code == 201 + body = r.json() + test_state['user3_token'] = body["token"]["access_token"] + test_state['user3_uuid'] = body["profile"]["id"] + + # coach create diet for user not in team -> 403 + async def test_02_coach_create_diet_for_user_forbidden_not_in_team(self, client, test_state): + payload = {"name": "Diet1", "description": "Test Diet"} + r = await client.post( + f"/diets/{test_state['user3_uuid']}", # user3 n'est pas dans l'équipe du coach + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 403 + + # coach create diet for user in team -> 201 + async def test_03_coach_create_diet_for_user(self, client, test_state): + payload = {"name": "Diet1", "description": "Test Diet"} + r = await client.post( + f"/diets/{test_state['user_uuid']}", # user est dans l'équipe du coach + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['diet_id'] = body["id"] + + # user create diet -> 403 + async def test_04_user_create_diet_forbidden(self, client, test_state): + payload = {"name": "Diet2", "description": "Test Diet 2"} + r = await client.post( + f"/diets/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # coach update diet -> 200 + async def test_05_coach_update_diet(self, client, test_state): + payload = {"name": "Diet1 Updated", "description": "Test Diet Updated"} + r = await client.patch( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + + # admin create diet for user -> 201 + async def test_06_admin_create_diet_for_user(self, client, test_state): + payload = {"name": "AdminDiet", "description": "Admin Test Diet"} + r = await client.post( + f"/diets/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['admin_diet_id'] = body["id"] + + # admin update diet -> 200 + async def test_07_admin_update_diet(self, client, test_state): + payload = {"name": "AdminDiet Updated", "description": "Admin Test Diet Updated"} + r = await client.patch( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # coach update admin diet -> 200 + async def test_08_coach_update_admin_diet(self, client, test_state): + payload = {"name": "AdminDiet Coach Update", "description": "Coach trying to update admin diet"} + r = await client.patch( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + + # coach get user diets -> 200 + async def test_09_coach_get_user_diets(self, client, test_state): + r = await client.get( + f"/diets/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # admin get user diets -> 200 + async def test_10_admin_get_user_diets(self, client, test_state): + r = await client.get( + f"/diets/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # user get user diets -> 403 + async def test_11_user_get_user_diets_forbidden(self, client, test_state): + r = await client.get( + f"/diets/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # user get mine diets -> 200 + async def test_12_user_get_mine_diets(self, client, test_state): + r = await client.get( + "/diets/mine", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # coach get mine diets (should be empty) -> 200 + async def test_13_coach_get_mine_diets(self, client, test_state): + r = await client.get( + "/diets/mine", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + # Should be empty since coach doesn't own diets for himself + assert len(r.json()) == 0 + + # MACRO PLAN TESTS + + # coach create macroplan in diet -> 201 + async def test_14_coach_create_macroplan_in_diet(self, client, test_state): + payload = { + "name": "MacroPlan1", + "carbohydrates": 250.0, + "lipids": 80.0, + "protein": 120.0, + "fiber": 35.0, + "water": 2.5, + "kilocalorie": 2000.0 + } + r = await client.post( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['macro_plan_id'] = body["id"] + + # admin create macroplan in diet -> 201 + async def test_15_admin_create_macroplan_in_diet(self, client, test_state): + payload = { + "name": "AdminMacroPlan", + "carbohydrates": 300.0, + "lipids": 90.0, + "protein": 150.0, + "fiber": 40.0, + "water": 3.0, + "kilocalorie": 2200.0 + } + r = await client.post( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/macro_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['admin_macro_plan_id'] = body["id"] + + # user create macroplan -> 403 + async def test_16_user_create_macroplan_forbidden(self, client, test_state): + payload = { + "name": "UserMacroPlan", + "carbohydrates": 200.0, + "lipids": 70.0, + "protein": 100.0, + "fiber": 30.0, + "water": 2.0, + "kilocalorie": 1800.0 + } + r = await client.post( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # coach update macroplan -> 200 + async def test_17_coach_update_macroplan(self, client, test_state): + payload = { + "name": "MacroPlan1 Updated", + "carbohydrates": 260.0, + "kilocalorie": 2100.0 + } + r = await client.patch( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['macro_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + + # admin update macroplan -> 200 + async def test_18_admin_update_macroplan(self, client, test_state): + payload = { + "name": "AdminMacroPlan Updated", + "protein": 160.0 + } + r = await client.patch( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['admin_macro_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # user update macroplan -> 403 + async def test_19_user_update_macroplan_forbidden(self, client, test_state): + payload = {"name": "User Update Attempt"} + r = await client.patch( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['macro_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # user get mine macroplans -> 200 + async def test_20_user_get_mine_macroplans(self, client, test_state): + r = await client.get( + "/diets/macro_plans/mine", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # user get all macroplans by diet -> 200 + async def test_21_user_get_all_macroplans_by_diet(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # coach get macroplans by diet -> 200 + async def test_22_coach_get_macroplans_by_diet(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # admin get specific macroplan -> 200 + async def test_23_admin_get_specific_macroplan(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['macro_plan_id']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # MEAL PLAN TESTS + + # coach create mealplan in diet -> 201 + async def test_24_coach_create_mealplan_in_diet(self, client, test_state): + payload = { + "name": "MealPlan1", + "meals": [ + {"timing": "breakfast", "food": "oatmeal with fruits"}, + {"timing": "lunch", "food": "chicken with rice"}, + {"timing": "dinner", "food": "salmon with vegetables"} + ] + } + r = await client.post( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['meal_plan_id'] = body["id"] + + # admin create mealplan in diet -> 201 + async def test_25_admin_create_mealplan_in_diet(self, client, test_state): + payload = { + "name": "AdminMealPlan", + "meals": [ + {"timing": "breakfast", "food": "protein shake"}, + {"timing": "snack", "food": "nuts and fruits"} + ] + } + r = await client.post( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/meal_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + body = r.json() + test_state['admin_meal_plan_id'] = body["id"] + + # user create mealplan -> 403 + async def test_26_user_create_mealplan_forbidden(self, client, test_state): + payload = { + "name": "UserMealPlan", + "meals": [{"timing": "breakfast", "food": "cereal"}] + } + r = await client.post( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # coach update mealplan -> 200 + async def test_27_coach_update_mealplan(self, client, test_state): + payload = { + "name": "MealPlan1 Updated", + "meals": [ + {"timing": "breakfast", "food": "greek yogurt with berries"}, + {"timing": "lunch", "food": "turkey sandwich"} + ] + } + r = await client.patch( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['meal_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + + # admin update mealplan -> 200 + async def test_28_admin_update_mealplan(self, client, test_state): + payload = { + "name": "AdminMealPlan Updated" + } + r = await client.patch( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['admin_meal_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # user update mealplan -> 403 + async def test_29_user_update_mealplan_forbidden(self, client, test_state): + payload = {"name": "User Update Attempt"} + r = await client.patch( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['meal_plan_id']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # user get mine mealplans -> 200 + async def test_30_user_get_mine_mealplans(self, client, test_state): + r = await client.get( + "/diets/meal_plans/mine", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # user get all mealplans by diet -> 200 + async def test_31_user_get_all_mealplans_by_diet(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # coach get mealplans by diet -> 200 + async def test_32_coach_get_mealplans_by_diet(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 200 + assert isinstance(r.json(), list) + + # admin get specific mealplan -> 200 + async def test_33_admin_get_specific_mealplan(self, client, test_state): + r = await client.get( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['meal_plan_id']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 200 + + # DELETE TESTS + + # user delete diet -> 403 + async def test_34_user_delete_diet_forbidden(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['user_token']}"} + ) + assert r.status_code == 403 + + # admin delete macroplan -> 204 + async def test_35_admin_delete_macroplan(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['admin_macro_plan_id']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 204 + + # coach delete macroplan -> 204 + async def test_36_coach_delete_macroplan(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/macro_plans/{test_state['macro_plan_id']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 204 + + # admin delete mealplan -> 204 + async def test_37_admin_delete_mealplan(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['admin_meal_plan_id']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 204 + + # coach delete mealplan -> 204 + async def test_38_coach_delete_mealplan(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}/meal_plans/{test_state['meal_plan_id']}", + headers={"Authorization": f"Bearer {test_state['coach_token']}"} + ) + assert r.status_code == 204 + + # admin delete coach diet -> 204 + async def test_39_admin_delete_coach_diet(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['diet_id']}/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 204 + + # admin delete admin diet -> 204 + async def test_40_admin_delete_admin_diet(self, client, test_state): + r = await client.delete( + f"/diets/{test_state['admin_diet_id']}/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 204 + + # Tests additionnels pour une meilleure couverture + + # user3 access user1 diets -> 403 + async def test_41_user3_access_user1_diets_forbidden(self, client, test_state): + # Create a diet first for testing + payload = {"name": "TestDiet", "description": "Test"} + r = await client.post( + f"/diets/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 201 + + # user3 tries to access user1's diets + r = await client.get( + f"/diets/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['user3_token']}"} + ) + assert r.status_code == 403 + + # Test error cases + + # create diet with invalid data -> 400 or 422 + async def test_42_create_diet_invalid_data(self, client, test_state): + payload = {"name": ""} # Empty name should fail + r = await client.post( + f"/diets/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + # Depending on validation, this might be 400 or 422 + assert r.status_code in [400, 422] + + # update non-existent diet -> 404 + async def test_43_update_nonexistent_diet(self, client, test_state): + from uuid import uuid4 + fake_diet_id = str(uuid4()) + payload = {"name": "Updated"} + r = await client.patch( + f"/diets/{fake_diet_id}/user/{test_state['user_uuid']}", + json=payload, + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 404 + + # delete non-existent diet -> 404 + async def test_44_delete_nonexistent_diet(self, client, test_state): + from uuid import uuid4 + fake_diet_id = str(uuid4()) + r = await client.delete( + f"/diets/{fake_diet_id}/user/{test_state['user_uuid']}", + headers={"Authorization": f"Bearer {test_state['admin_token']}"} + ) + assert r.status_code == 404 \ No newline at end of file From 158f218fc302c14a15205a9c8d351f647d14adef Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 16:38:24 +0200 Subject: [PATCH 26/28] create pipeline for testing executing when pr open update or reopen --- .github/workflows/test.yml | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..eb880d9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,60 @@ +name: Run Tests + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main, develop] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Create and activate virtual environment + run: | + python -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: | + source venv/bin/activate + pip install --upgrade pip + pip install . + + - name: Run tests + env: + ENV: test + SECRET_KEY: test_secret_key + ACCESS_TOKEN_EXPIRE_MINUTES: 60 + run: | + source venv/bin/activate + uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload & + pytest src/entrypoints/api/tests/profile.py \ + src/entrypoints/api/tests/group.py \ + src/entrypoints/api/tests/exercise.py \ + src/entrypoints/api/tests/training.py \ + src/entrypoints/api/tests/diet.py \ + -v \ + --tb=short + + - name: Test Summary + if: always() + run: | + echo "Tests completed!" + echo "Check the logs above for details." From 5a71cb4bff5ca3c48722cb2c96c9684ace91ecef Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 16:40:44 +0200 Subject: [PATCH 27/28] test 3.12 version --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb880d9..4ea3d63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Cache pip dependencies uses: actions/cache@v3 From 41a4ffa013431c9f43938ba8ee8191bce7ac8aba Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 16:42:18 +0200 Subject: [PATCH 28/28] downgrate requirement python version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8c61792..cc4b9ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "tracknatrainapi" version = "0.4.0" -requires-python = ">=3.13" +requires-python = ">=3.12" dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1", "pytest>=7.0", "pytest-asyncio>=0.20", "httpx>=0.24",