From 9456809a2180d942864113871e907dd958f91d70 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:43:54 +0100 Subject: [PATCH 01/19] bump python version requirements in all files (#654) --- poetry.lock | 46 ++++++++--------- requirements.txt | 132 +++++++++++++++++++++++++---------------------- setup.py | 2 +- 3 files changed, 93 insertions(+), 87 deletions(-) diff --git a/poetry.lock b/poetry.lock index c114c92e..3a378c8d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1117,13 +1117,13 @@ dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptio [[package]] name = "marshmallow" -version = "3.23.0" +version = "3.23.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" files = [ - {file = "marshmallow-3.23.0-py3-none-any.whl", hash = "sha256:82f20a2397834fe6d9611b241f2f7e7b680ed89c49f84728a1ad937be6b4bdf4"}, - {file = "marshmallow-3.23.0.tar.gz", hash = "sha256:98d8827a9f10c03d44ead298d2e99c6aea8197df18ccfad360dae7f89a50da2e"}, + {file = "marshmallow-3.23.1-py3-none-any.whl", hash = "sha256:fece2eb2c941180ea1b7fcbd4a83c51bfdd50093fdd3ad2585ee5e1df2508491"}, + {file = "marshmallow-3.23.1.tar.gz", hash = "sha256:3a8dfda6edd8dcdbf216c0ede1d1e78d230a6dc9c5a088f58c4083b974a0d468"}, ] [package.dependencies] @@ -1131,7 +1131,7 @@ packaging = ">=17.0" [package.extras] dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] -docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-version-warning (==1.1.2)"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.14)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "simplejson"] [[package]] @@ -1653,29 +1653,29 @@ httpx = ">=0.21.0" [[package]] name = "ruff" -version = "0.7.1" +version = "0.7.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, - {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, - {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, - {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, - {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, - {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, - {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, + {file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"}, + {file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"}, + {file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"}, + {file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"}, + {file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"}, + {file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"}, + {file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"}, + {file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"}, + {file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"}, + {file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"}, + {file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"}, + {file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"}, ] [[package]] diff --git a/requirements.txt b/requirements.txt index 84819b60..d3fc7b4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,63 +1,69 @@ -aiosqlite==0.20.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -anyio==3.7.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -asn1crypto==1.5.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -async-timeout==4.0.3 ; python_full_version >= "3.8.1" and python_version < "3.12.0" -asyncpg==0.29.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -base58==2.1.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -bech32==1.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -bip32==3.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -bitstring==3.1.9 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -bolt11==2.0.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -cbor2==5.6.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -certifi==2024.7.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -cffi==1.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -cfgv==3.4.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -click==8.1.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -coincurve==20.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and (platform_system == "Windows" or sys_platform == "win32") -cryptography==41.0.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -deprecated==1.2.14 ; python_full_version >= "3.8.1" and python_version < "4.0" -distlib==0.3.8 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -ecdsa==0.18.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -environs==9.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -exceptiongroup==1.2.1 ; python_full_version >= "3.8.1" and python_version < "3.11" -fastapi==0.104.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -filelock==3.15.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -greenlet==3.0.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -h11==0.14.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -httpcore==1.0.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -httpx[socks]==0.25.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -identify==2.6.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -idna==3.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -importlib-metadata==6.11.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -importlib-resources==6.4.0 ; python_full_version >= "3.8.1" and python_version < "4.0" -limits==3.13.0 ; python_full_version >= "3.8.1" and python_version < "4.0" -loguru==0.7.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -marshmallow==3.21.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -mnemonic==0.20 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -nodeenv==1.9.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -packaging==24.1 ; python_full_version >= "3.8.1" and python_version < "4.0" -platformdirs==4.2.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pre-commit==3.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pycparser==2.22 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pycryptodomex==3.20.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pydantic==1.10.17 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -python-dotenv==1.0.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pyyaml==6.0.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -secp256k1==0.14.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -setuptools==68.2.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -six==1.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -slowapi==0.1.9 ; python_full_version >= "3.8.1" and python_version < "4.0" -sniffio==1.3.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -socksio==1.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -sqlalchemy[asyncio]==1.4.52 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -starlette==0.27.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -typing-extensions==4.12.2 ; python_full_version >= "3.8.1" and python_version < "4.0" -uvicorn==0.23.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -virtualenv==20.26.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -websocket-client==1.8.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -websockets==12.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -wheel==0.41.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -win32-setctime==1.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32" -wrapt==1.16.0 ; python_full_version >= "3.8.1" and python_version < "4.0" -zipp==3.19.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +aiosqlite==0.20.0 ; python_version >= "3.10" and python_version < "4.0" +anyio==4.6.2.post1 ; python_version >= "3.10" and python_version < "4.0" +asn1crypto==1.5.1 ; python_version >= "3.10" and python_version < "4.0" +async-timeout==4.0.3 ; python_version >= "3.10" and python_version < "3.12.0" +asyncpg==0.29.0 ; python_version >= "3.10" and python_version < "4.0" +base58==2.1.1 ; python_version >= "3.10" and python_version < "4.0" +bech32==1.2.0 ; python_version >= "3.10" and python_version < "4.0" +bip32==4.0 ; python_version >= "3.10" and python_version < "4.0" +bitstring==3.1.9 ; python_version >= "3.10" and python_version < "4.0" +bolt11==2.1.0 ; python_version >= "3.10" and python_version < "4.0" +cbor2==5.6.5 ; python_version >= "3.10" and python_version < "4.0" +certifi==2024.8.30 ; python_version >= "3.10" and python_version < "4.0" +cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0" +cfgv==3.4.0 ; python_version >= "3.10" and python_version < "4.0" +click==8.1.7 ; python_version >= "3.10" and python_version < "4.0" +coincurve==20.0.0 ; python_version >= "3.10" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32") +cryptography==43.0.3 ; python_version >= "3.10" and python_version < "4.0" +deprecated==1.2.14 ; python_version >= "3.10" and python_version < "4.0" +distlib==0.3.9 ; python_version >= "3.10" and python_version < "4.0" +ecdsa==0.19.0 ; python_version >= "3.10" and python_version < "4.0" +environs==9.5.0 ; python_version >= "3.10" and python_version < "4.0" +exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "3.11" +fastapi==0.115.4 ; python_version >= "3.10" and python_version < "4.0" +filelock==3.16.1 ; python_version >= "3.10" and python_version < "4.0" +googleapis-common-protos==1.65.0 ; python_version >= "3.10" and python_version < "4.0" +greenlet==3.1.1 ; python_version >= "3.10" and python_version < "4.0" +grpcio-tools==1.67.1 ; python_version >= "3.10" and python_version < "4.0" +grpcio==1.67.1 ; python_version >= "3.10" and python_version < "4.0" +h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0" +httpcore==1.0.6 ; python_version >= "3.10" and python_version < "4.0" +httpx[socks]==0.25.2 ; python_version >= "3.10" and python_version < "4.0" +identify==2.6.1 ; python_version >= "3.10" and python_version < "4.0" +idna==3.10 ; python_version >= "3.10" and python_version < "4.0" +importlib-metadata==6.11.0 ; python_version >= "3.10" and python_version < "4.0" +importlib-resources==6.4.5 ; python_version >= "3.10" and python_version < "4.0" +limits==3.13.0 ; python_version >= "3.10" and python_version < "4.0" +loguru==0.7.2 ; python_version >= "3.10" and python_version < "4.0" +marshmallow==3.23.0 ; python_version >= "3.10" and python_version < "4.0" +mnemonic==0.20 ; python_version >= "3.10" and python_version < "4.0" +mypy-protobuf==3.6.0 ; python_version >= "3.10" and python_version < "4.0" +nodeenv==1.9.1 ; python_version >= "3.10" and python_version < "4.0" +packaging==24.1 ; python_version >= "3.10" and python_version < "4.0" +platformdirs==4.3.6 ; python_version >= "3.10" and python_version < "4.0" +pre-commit==3.8.0 ; python_version >= "3.10" and python_version < "4.0" +protobuf==5.28.3 ; python_version >= "3.10" and python_version < "4.0" +pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" +pycryptodomex==3.21.0 ; python_version >= "3.10" and python_version < "4.0" +pydantic==1.10.18 ; python_version >= "3.10" and python_version < "4.0" +python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0" +pyyaml==6.0.2 ; python_version >= "3.10" and python_version < "4.0" +secp256k1==0.14.0 ; python_version >= "3.10" and python_version < "4.0" +setuptools==75.3.0 ; python_version >= "3.10" and python_version < "4.0" +six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" +slowapi==0.1.9 ; python_version >= "3.10" and python_version < "4.0" +sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0" +socksio==1.0.0 ; python_version >= "3.10" and python_version < "4.0" +sqlalchemy[asyncio]==2.0.36 ; python_version >= "3.10" and python_version < "4.0" +starlette==0.41.2 ; python_version >= "3.10" and python_version < "4.0" +types-protobuf==5.28.3.20241030 ; python_version >= "3.10" and python_version < "4.0" +typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0" +uvicorn==0.31.1 ; python_version >= "3.10" and python_version < "4.0" +virtualenv==20.27.1 ; python_version >= "3.10" and python_version < "4.0" +websocket-client==1.8.0 ; python_version >= "3.10" and python_version < "4.0" +websockets==12.0 ; python_version >= "3.10" and python_version < "4.0" +wheel==0.41.3 ; python_version >= "3.10" and python_version < "4.0" +win32-setctime==1.1.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform == "win32" +wrapt==1.16.0 ; python_version >= "3.10" and python_version < "4.0" +zipp==3.20.2 ; python_version >= "3.10" and python_version < "4.0" diff --git a/setup.py b/setup.py index d3878bb9..cd764107 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], - python_requires=">=3.7", + python_requires=">=3.10", install_requires=requirements, include_package_data=True, entry_points=entry_points, From 80ff0f1a7c558b9e6618320bdee6eef180e458c9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:47:31 +0100 Subject: [PATCH 02/19] Fix db lock tests (#665) * skip db lock check in regtests * revert? * try this * do not dispose conftests * remove return * test this * try auto * dispose stuff * try uri=true * remove the other flag * move test * reduce tests * reduce more * keep one * skip on github? * only skip in regtest * trigger again * restore cashu/mint/ledger.py --- cashu/core/db.py | 7 ++----- tests/test_db.py | 26 ++++++++++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/cashu/core/db.py b/cashu/core/db.py index 7a069d60..dc9dc212 100644 --- a/cashu/core/db.py +++ b/cashu/core/db.py @@ -228,14 +228,11 @@ def _is_lock_exception(e): ) else: logger.error(f"Error in session trial: {trial} ({random_int}): {e}") - raise e + raise finally: logger.trace(f"Closing session trial: {trial} ({random_int})") await session.close() - # if not inherited: - # logger.trace("Closing session") - # await session.close() - # self._connection = None + raise Exception( f"failed to acquire database lock on {lock_table} after {timeout}s and {trial} trials ({random_int})" ) diff --git a/tests/test_db.py b/tests/test_db.py index d44e18cb..90c740df 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -14,7 +14,7 @@ from cashu.mint.ledger import Ledger from cashu.wallet.wallet import Wallet from tests.conftest import SERVER_ENDPOINT -from tests.helpers import is_github_actions, is_postgres, pay_if_regtest +from tests.helpers import is_github_actions, is_postgres, is_regtest, pay_if_regtest async def assert_err(f, msg): @@ -49,6 +49,7 @@ async def wallet(): ) await wallet.load_mint() yield wallet + await wallet.db.engine.dispose() @pytest.mark.asyncio @@ -146,13 +147,10 @@ async def get_connection(): @pytest.mark.asyncio @pytest.mark.skipif( - not settings.mint_database.startswith("postgres"), + not not is_postgres, reason="SQLite does not support row locking", ) async def test_db_get_connection_lock_row(wallet: Wallet, ledger: Ledger): - if ledger.db.type == db.SQLITE: - pytest.skip("SQLite does not support row locking") - mint_quote = await wallet.request_mint(64) async def get_connection(): @@ -189,6 +187,10 @@ async def get_connection(): @pytest.mark.asyncio +@pytest.mark.skipif( + is_github_actions and is_regtest and not is_postgres, + reason=("Fails on GitHub Actions for regtest + SQLite"), +) async def test_db_verify_spent_proofs_and_set_pending_race_condition( wallet: Wallet, ledger: Ledger ): @@ -211,6 +213,10 @@ async def test_db_verify_spent_proofs_and_set_pending_race_condition( @pytest.mark.asyncio +@pytest.mark.skipif( + is_github_actions and is_regtest and not is_postgres, + reason=("Fails on GitHub Actions for regtest + SQLite"), +) async def test_db_verify_spent_proofs_and_set_pending_delayed_no_race_condition( wallet: Wallet, ledger: Ledger ): @@ -234,6 +240,10 @@ async def delayed_verify_spent_proofs_and_set_pending(): @pytest.mark.asyncio +@pytest.mark.skipif( + is_github_actions and is_regtest and not is_postgres, + reason=("Fails on GitHub Actions for regtest + SQLite"), +) async def test_db_verify_spent_proofs_and_set_pending_no_race_condition_different_proofs( wallet: Wallet, ledger: Ledger ): @@ -252,7 +262,7 @@ async def test_db_verify_spent_proofs_and_set_pending_no_race_condition_differen @pytest.mark.asyncio @pytest.mark.skipif( - not settings.mint_database.startswith("postgres"), + not is_postgres, reason="SQLite does not support row locking", ) async def test_db_get_connection_lock_different_row(wallet: Wallet, ledger: Ledger): @@ -300,6 +310,10 @@ async def get_connection2(): @pytest.mark.asyncio +@pytest.mark.skipif( + is_github_actions and is_regtest and not is_postgres, + reason=("Fails on GitHub Actions for regtest + SQLite"), +) async def test_db_lock_table(wallet: Wallet, ledger: Ledger): # fill wallet mint_quote = await wallet.request_mint(64) From b5afdfd59888062913580dd448a020dca55ad68d Mon Sep 17 00:00:00 2001 From: lollerfirst <43107113+lollerfirst@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:51:53 +0100 Subject: [PATCH 03/19] Wallet check payment hash (#649) * check preimage * make format * adjust logs --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- cashu/wallet/cli/cli.py | 7 ++++++- cashu/wallet/helpers.py | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 85e0b1b7..fef2d45f 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -11,6 +11,7 @@ from os.path import isdir, join from typing import Optional, Union +import bolt11 import click from click import Context from loguru import logger @@ -49,6 +50,7 @@ verify_mint, ) from ..helpers import ( + check_payment_preimage, deserialize_token_from_string, init_wallet, list_mints, @@ -209,6 +211,7 @@ async def pay( wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() await print_balance(ctx) + payment_hash = bolt11.decode(invoice).payment_hash quote = await wallet.melt_quote(invoice, amount) logger.debug(f"Quote: {quote}") total_amount = quote.amount + quote.fee_reserve @@ -252,9 +255,11 @@ async def pay( melt_response.payment_preimage and melt_response.payment_preimage != "0" * 64 ): + if not check_payment_preimage(payment_hash, melt_response.payment_preimage): + print(" Error: Invalid preimage!", end="", flush=True) print(f" (Preimage: {melt_response.payment_preimage}).") else: - print(".") + print(" Mint did not provide a preimage.") elif MintQuoteState(melt_response.state) == MintQuoteState.pending: print(" Invoice pending.") elif MintQuoteState(melt_response.state) == MintQuoteState.unpaid: diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index 85c73b72..fc19f913 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -1,3 +1,4 @@ +import hashlib import os from typing import Optional @@ -169,3 +170,11 @@ async def send( await wallet.set_reserved(send_proofs, reserved=True) return wallet.available_balance, token + +def check_payment_preimage( + payment_hash: str, + preimage: str, +) -> bool: + return bytes.fromhex(payment_hash) == hashlib.sha256( + bytes.fromhex(preimage) + ).digest() \ No newline at end of file From 9cdfba52a3a9d0c70dc9f3cffd9058520bdb77c5 Mon Sep 17 00:00:00 2001 From: elnosh Date: Tue, 5 Nov 2024 07:52:09 -0500 Subject: [PATCH 04/19] add nut-14 as supported in info endpoint (#647) --- cashu/core/nuts.py | 1 + cashu/mint/features.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cashu/core/nuts.py b/cashu/core/nuts.py index 6c0b5287..ffe788aa 100644 --- a/cashu/core/nuts.py +++ b/cashu/core/nuts.py @@ -9,5 +9,6 @@ P2PK_NUT = 11 DLEQ_NUT = 12 DETERMINSTIC_SECRETS_NUT = 13 +HTLC_NUT = 14 MPP_NUT = 15 WEBSOCKETS_NUT = 17 diff --git a/cashu/mint/features.py b/cashu/mint/features.py index 43fdec41..85e8f71b 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -8,6 +8,7 @@ from ..core.nuts import ( DLEQ_NUT, FEE_RETURN_NUT, + HTLC_NUT, MELT_NUT, MINT_NUT, MPP_NUT, @@ -58,6 +59,7 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: SCRIPT_NUT: supported_dict, P2PK_NUT: supported_dict, DLEQ_NUT: supported_dict, + HTLC_NUT: supported_dict, } # signal which method-unit pairs support MPP From ed0d25dec76200d117b74552b5047f76021686f6 Mon Sep 17 00:00:00 2001 From: lollerfirst <43107113+lollerfirst@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:00:37 +0100 Subject: [PATCH 05/19] [FIX] Wallet sort outputs before swapping (#648) * sort proofs * outputs-ordering * mypy fix * clean up * test if output amounts are sorted * clean up test --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- cashu/wallet/wallet.py | 22 +++++++++++--- tests/test_wallet_requests.py | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 tests/test_wallet_requests.py diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 8d61e9e7..f0a9d76c 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -456,7 +456,7 @@ def split_wallet_state(self, amount: int) -> List[int]: # sort by increasing amount amounts_we_want.sort() - logger.debug( + logger.trace( f"Amounts we have: {[(a, amounts_we_have.count(a)) for a in set(amounts_we_have)]}" ) amounts: list[int] = [] @@ -470,7 +470,7 @@ def split_wallet_state(self, amount: int) -> List[int]: amounts += amount_split(remaining_amount) amounts.sort() - logger.debug(f"Amounts we want: {amounts}") + logger.trace(f"Amounts we want: {amounts}") if sum(amounts) != amount: raise Exception(f"Amounts do not sum to {amount}.") @@ -643,7 +643,7 @@ async def split( proofs = self.add_witnesses_to_proofs(proofs) input_fees = self.get_fees_for_proofs(proofs) - logger.debug(f"Input fees: {input_fees}") + logger.trace(f"Input fees: {input_fees}") # create a suitable amounts to keep and send. keep_outputs, send_outputs = self.determine_output_amounts( proofs, @@ -674,8 +674,22 @@ async def split( # potentially add witnesses to outputs based on what requirement the proofs indicate outputs = self.add_witnesses_to_outputs(proofs, outputs) + # sort outputs by amount, remember original order + sorted_outputs_with_indices = sorted( + enumerate(outputs), key=lambda p: p[1].amount + ) + original_indices, sorted_outputs = zip(*sorted_outputs_with_indices) + # Call swap API - promises = await super().split(proofs, outputs) + sorted_promises = await super().split(proofs, sorted_outputs) + + # sort promises back to original order + promises = [ + promise + for _, promise in sorted( + zip(original_indices, sorted_promises), key=lambda x: x[0] + ) + ] # Construct proofs from returned promises (i.e., unblind the signatures) new_proofs = await self._construct_proofs( diff --git a/tests/test_wallet_requests.py b/tests/test_wallet_requests.py new file mode 100644 index 00000000..3f2d6ea8 --- /dev/null +++ b/tests/test_wallet_requests.py @@ -0,0 +1,57 @@ +import json + +import pytest +import pytest_asyncio +import respx +from httpx import Request, Response + +from cashu.core.base import BlindedSignature +from cashu.core.crypto.b_dhke import hash_to_curve +from cashu.wallet.wallet import Wallet +from cashu.wallet.wallet import Wallet as Wallet1 +from tests.conftest import SERVER_ENDPOINT +from tests.helpers import pay_if_regtest + + +@pytest_asyncio.fixture(scope="function") +async def wallet1(mint): + wallet1 = await Wallet1.with_db( + url=SERVER_ENDPOINT, + db="test_data/wallet1", + name="wallet1", + ) + await wallet1.load_mint() + yield wallet1 + + +@pytest.mark.asyncio +async def test_swap_outputs_are_sorted(wallet1: Wallet): + await wallet1.load_mint() + mint_quote = await wallet1.request_mint(16) + await pay_if_regtest(mint_quote.request) + await wallet1.mint(16, quote_id=mint_quote.quote, split=[16]) + assert wallet1.balance == 16 + + test_url = f"{wallet1.url}/v1/swap" + key = hash_to_curve("test".encode("utf-8")) + mock_blind_signature = BlindedSignature( + id=wallet1.keyset_id, + amount=8, + C_=key.serialize().hex(), + ) + mock_response_data = {"signatures": [mock_blind_signature.dict()]} + with respx.mock() as mock: + route = mock.post(test_url).mock( + return_value=Response(200, json=mock_response_data) + ) + await wallet1.select_to_send(wallet1.proofs, 5) + + assert route.called + assert route.call_count == 1 + request: Request = route.calls[0].request + assert request.method == "POST" + assert request.url == test_url + request_data = json.loads(request.content.decode("utf-8")) + output_amounts = [o["amount"] for o in request_data["outputs"]] + # assert that output amounts are sorted + assert output_amounts == sorted(output_amounts) From 602687b21505dea6e84aff0a91a36708f8b03d05 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:02:54 +0100 Subject: [PATCH 06/19] refactor pubkey extraction and set n_sigs=1 for refund spend path (#644) --- cashu/core/htlc.py | 2 -- cashu/core/p2pk.py | 33 +------------------------ cashu/mint/conditions.py | 52 ++++++++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/cashu/core/htlc.py b/cashu/core/htlc.py index 9cc8fb5f..e2bcea21 100644 --- a/cashu/core/htlc.py +++ b/cashu/core/htlc.py @@ -7,8 +7,6 @@ class SigFlags(Enum): # require signatures only on the inputs (default signature flag) SIG_INPUTS = "SIG_INPUTS" - # require signatures on inputs and outputs - SIG_ALL = "SIG_ALL" class HTLCSecret(Secret): diff --git a/cashu/core/p2pk.py b/cashu/core/p2pk.py index 0da0ae23..23f379de 100644 --- a/cashu/core/p2pk.py +++ b/cashu/core/p2pk.py @@ -1,9 +1,6 @@ import hashlib -import time from enum import Enum -from typing import List, Union - -from loguru import logger +from typing import Union from .crypto.secp import PrivateKey, PublicKey from .secret import Secret, SecretKind @@ -24,34 +21,6 @@ def from_secret(cls, secret: Secret): # need to add it back in manually with tags=secret.tags return cls(**secret.dict(exclude={"tags"}), tags=secret.tags) - def get_p2pk_pubkey_from_secret(self) -> List[str]: - """Gets the P2PK pubkey from a Secret depending on the locktime. - - If locktime is passed, only the refund pubkeys are returned. - Else, the pubkeys in the data field and in the 'pubkeys' tag are returned. - - Args: - secret (Secret): P2PK Secret in ecash token - - Returns: - str: pubkey to use for P2PK, empty string if anyone can spend (locktime passed) - """ - # the pubkey in the data field is the pubkey to use for P2PK - pubkeys: List[str] = [self.data] - - # get all additional pubkeys from tags for multisig - pubkeys += self.tags.get_tag_all("pubkeys") - - # check if locktime is passed and if so, only return refund pubkeys - now = time.time() - if self.locktime and self.locktime < now: - logger.trace(f"p2pk locktime ran out ({self.locktime}<{now}).") - # check tags if a refund pubkey is present. - # If yes, we demand the signature to be from the refund pubkey - return self.tags.get_tag_all("refund") - - return pubkeys - @property def locktime(self) -> Union[None, int]: locktime = self.tags.get_tag("locktime") diff --git a/cashu/mint/conditions.py b/cashu/mint/conditions.py index 1b9b61f2..ea7c53a5 100644 --- a/cashu/mint/conditions.py +++ b/cashu/mint/conditions.py @@ -44,11 +44,28 @@ def _verify_p2pk_spending_conditions(self, proof: Proof, secret: Secret) -> bool # extract pubkeys that we require signatures from depending on whether the # locktime has passed (refund) or not (pubkeys in secret.data and in tags) - # This is implemented in get_p2pk_pubkey_from_secret() - pubkeys = p2pk_secret.get_p2pk_pubkey_from_secret() - # we will get an empty list if the locktime has passed and no refund pubkey is present - if not pubkeys: - return True + + # the pubkey in the data field is the pubkey to use for P2PK + pubkeys: List[str] = [p2pk_secret.data] + + # get all additional pubkeys from tags for multisig + pubkeys += p2pk_secret.tags.get_tag_all("pubkeys") + + # check if locktime is passed and if so, only consider refund pubkeys + now = time.time() + if p2pk_secret.locktime and p2pk_secret.locktime < now: + logger.trace(f"p2pk locktime ran out ({p2pk_secret.locktime}<{now}).") + # If a refund pubkey is present, we demand the signature to be from it + refund_pubkeys = p2pk_secret.tags.get_tag_all("refund") + if not refund_pubkeys: + # no refund pubkey is present, anyone can spend + return True + return self._verify_secret_signatures( + proof, + refund_pubkeys, + proof.p2pksigs, + 1, # only 1 sig required for refund + ) return self._verify_secret_signatures( proof, pubkeys, proof.p2pksigs, p2pk_secret.n_sigs @@ -97,7 +114,10 @@ def _verify_htlc_spending_conditions(self, proof: Proof, secret: Secret) -> bool refund_pubkeys = htlc_secret.tags.get_tag_all("refund") if refund_pubkeys: return self._verify_secret_signatures( - proof, refund_pubkeys, proof.p2pksigs, htlc_secret.n_sigs + proof, + refund_pubkeys, + proof.p2pksigs, + 1, # only one refund signature required ) # no pubkeys given in secret, anyone can spend return True @@ -257,9 +277,23 @@ def _verify_output_p2pk_spending_conditions( # extract all pubkeys and n_sigs from secrets pubkeys_per_proof = [ - secret.get_p2pk_pubkey_from_secret() for secret in p2pk_secrets + [p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys") + for p2pk_secret in p2pk_secrets ] - n_sigs_per_proof = [secret.n_sigs for secret in p2pk_secrets] + n_sigs_per_proof = [p2pk_secret.n_sigs for p2pk_secret in p2pk_secrets] + + # if locktime passed, we only require the refund pubkeys and 1 signature + for p2pk_secret in p2pk_secrets: + now = time.time() + if p2pk_secret.locktime and p2pk_secret.locktime < now: + refund_pubkeys = p2pk_secret.tags.get_tag_all("refund") + if refund_pubkeys: + pubkeys_per_proof.append(refund_pubkeys) + n_sigs_per_proof.append(1) # only 1 sig required for refund + + # if no pubkeys are present, anyone can spend + if not pubkeys_per_proof: + return True # all pubkeys and n_sigs must be the same assert ( @@ -267,8 +301,6 @@ def _verify_output_p2pk_spending_conditions( ), "pubkeys in all proofs must match." assert len(set(n_sigs_per_proof)) == 1, "n_sigs in all proofs must match." - # TODO: add limit for maximum number of pubkeys - # validation successful pubkeys: List[str] = pubkeys_per_proof[0] From 0de574e006183840986064a9d0552e73d62620d6 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Tue, 5 Nov 2024 15:03:23 +0100 Subject: [PATCH 07/19] NUT-06: add urls field (#638) --- .env.example | 2 ++ cashu/core/models.py | 1 + cashu/core/settings.py | 1 + cashu/mint/router.py | 1 + 4 files changed, 5 insertions(+) diff --git a/.env.example b/.env.example index c10fa187..06d68324 100644 --- a/.env.example +++ b/.env.example @@ -39,6 +39,8 @@ MINT_INFO_DESCRIPTION="The short mint description" MINT_INFO_DESCRIPTION_LONG="A long mint description that can be a long piece of text." MINT_INFO_CONTACT=[["email","contact@me.com"], ["twitter","@me"], ["nostr", "npub..."]] MINT_INFO_MOTD="Message to users" +MINT_INFO_ICON_URL="https://mint.host/icon.jpg" +MINT_INFO_URLS=["https://mint.host", "http://mint8gv0sq5ul602uxt2fe0t80e3c2bi9fy0cxedp69v1vat6ruj81wv.onion"] MINT_PRIVATE_KEY=supersecretprivatekey diff --git a/cashu/core/models.py b/cashu/core/models.py index 4565f5b3..77addfa6 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -47,6 +47,7 @@ class GetInfoResponse(BaseModel): contact: Optional[List[MintInfoContact]] = None motd: Optional[str] = None icon_url: Optional[str] = None + urls: Optional[List[str]] = None time: Optional[int] = None nuts: Optional[Dict[int, Any]] = None diff --git a/cashu/core/settings.py b/cashu/core/settings.py index c49fd52a..f89d2a9f 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -153,6 +153,7 @@ class MintInformation(CashuSettings): mint_info_contact: List[List[str]] = Field(default=[]) mint_info_motd: str = Field(default=None) mint_info_icon_url: str = Field(default=None) + mint_info_urls: List[str] = Field(default=None) class WalletSettings(CashuSettings): diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 69f34589..5e7f23c7 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -57,6 +57,7 @@ async def info() -> GetInfoResponse: contact=contact_info, nuts=mint_features, icon_url=settings.mint_info_icon_url, + urls=settings.mint_info_urls, motd=settings.mint_info_motd, time=int(time.time()), ) From 2ad6d831e998934128512b020f100f2169b8b9b7 Mon Sep 17 00:00:00 2001 From: David Caseria Date: Tue, 5 Nov 2024 09:03:41 -0500 Subject: [PATCH 08/19] Add FakeWallet invoice features tag for supported payment secret (#609) --- cashu/lightning/fake.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index 1d3cbab1..51146f16 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -7,6 +7,9 @@ from bolt11 import ( Bolt11, + Feature, + FeatureState, + Features, MilliSatoshi, TagChar, Tags, @@ -90,6 +93,7 @@ async def create_invoice( ) -> InvoiceResponse: self.assert_unit_supported(amount.unit) tags = Tags() + tags.add(TagChar.features, Features.from_feature_list({Feature.payment_secret: FeatureState.supported})) if description_hash: tags.add(TagChar.description_hash, description_hash.hex()) From d8a90d4c80740c93dffc19f6d43ab6b2800ae318 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:40:17 +0100 Subject: [PATCH 09/19] turn on mpp by default (#667) --- cashu/core/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index f89d2a9f..8865f569 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -209,7 +209,7 @@ class LndRestFundingSource(MintSettings): mint_lnd_rest_macaroon: Optional[str] = Field(default=None) mint_lnd_rest_admin_macaroon: Optional[str] = Field(default=None) mint_lnd_rest_invoice_macaroon: Optional[str] = Field(default=None) - mint_lnd_enable_mpp: bool = Field(default=False) + mint_lnd_enable_mpp: bool = Field(default=True) class LndRPCFundingSource(MintSettings): @@ -222,7 +222,7 @@ class CLNRestFundingSource(MintSettings): mint_clnrest_url: Optional[str] = Field(default=None) mint_clnrest_cert: Optional[str] = Field(default=None) mint_clnrest_rune: Optional[str] = Field(default=None) - mint_clnrest_enable_mpp: bool = Field(default=False) + mint_clnrest_enable_mpp: bool = Field(default=True) class CoreLightningRestFundingSource(MintSettings): From 91976634a14287b78ef9ed2812c106261fedfc94 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:45:34 +0100 Subject: [PATCH 10/19] format (#668) --- cashu/lightning/fake.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index 51146f16..3d51c369 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -8,8 +8,8 @@ from bolt11 import ( Bolt11, Feature, - FeatureState, Features, + FeatureState, MilliSatoshi, TagChar, Tags, @@ -93,7 +93,12 @@ async def create_invoice( ) -> InvoiceResponse: self.assert_unit_supported(amount.unit) tags = Tags() - tags.add(TagChar.features, Features.from_feature_list({Feature.payment_secret: FeatureState.supported})) + tags.add( + TagChar.features, + Features.from_feature_list( + {Feature.payment_secret: FeatureState.supported} + ), + ) if description_hash: tags.add(TagChar.description_hash, description_hash.hex()) From 6a4f1bdbe99307eca7c836acb9eb88f487590a81 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:55:22 +0100 Subject: [PATCH 11/19] bump version to 0.16.3 (#669) --- README.md | 2 +- cashu/core/settings.py | 2 +- pyproject.toml | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 852e6224..2fc9d10f 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ This command runs the mint on your local computer. Skip this step if you want to ## Docker ``` -docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.2 poetry run mint +docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.3 poetry run mint ``` ## From this repository diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 8865f569..1344f2b2 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -8,7 +8,7 @@ env = Env() -VERSION = "0.16.2" +VERSION = "0.16.3" def find_env_file(): diff --git a/pyproject.toml b/pyproject.toml index 2866beb0..74b84e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cashu" -version = "0.16.2" +version = "0.16.3" description = "Ecash wallet and mint" authors = ["calle "] license = "MIT" diff --git a/setup.py b/setup.py index cd764107..2658e827 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setuptools.setup( name="cashu", - version="0.16.2", + version="0.16.3", description="Ecash wallet and mint", long_description=long_description, long_description_content_type="text/markdown", From 901b16713cd962f613367acd0c77ffa46c42ba2d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:21:54 +0100 Subject: [PATCH 12/19] add methods key to info endpoitn a la https://github.com/cashubtc/nuts/pull/190 (#672) --- cashu/mint/features.py | 2 +- cashu/wallet/mint_info.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cashu/mint/features.py b/cashu/mint/features.py index 85e8f71b..f216de9d 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -76,7 +76,7 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: ) if mpp_features: - mint_features[MPP_NUT] = mpp_features + mint_features[MPP_NUT] = dict(methods=mpp_features) # specify which websocket features are supported # these two are supported by default diff --git a/cashu/wallet/mint_info.py b/cashu/wallet/mint_info.py index ccd00087..a9154c8b 100644 --- a/cashu/wallet/mint_info.py +++ b/cashu/wallet/mint_info.py @@ -31,10 +31,10 @@ def supports_mpp(self, method: str, unit: Unit) -> bool: if not self.nuts: return False nut_15 = self.nuts.get(MPP_NUT) - if not nut_15 or not self.supports_nut(MPP_NUT): + if not nut_15 or not self.supports_nut(MPP_NUT) or not nut_15.get("methods"): return False - for entry in nut_15: + for entry in nut_15["methods"]: entry_obj = Nut15MppSupport.parse_obj(entry) if entry_obj.method == method and entry_obj.unit == unit.name: return True From c4b639b068c6014ddeaff1e880c2025f994b5fa5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:38:59 +0100 Subject: [PATCH 13/19] parse env example (#677) --- cashu/core/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cashu/core/base.py b/cashu/core/base.py index bde77872..8922752c 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -732,6 +732,12 @@ def __init__( input_fee_ppk: Optional[int] = None, id: str = "", ): + DEFAULT_SEED = "supersecretprivatekey" + if seed == DEFAULT_SEED: + raise Exception( + f"Seed is set to default value '{DEFAULT_SEED}'. Please change it." + ) + self.derivation_path = derivation_path if encrypted_seed and not settings.mint_seed_decryption_key: From 0e2bdb04384491ca47d10ca30bd97ed99883594e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:47:03 +0100 Subject: [PATCH 14/19] recommend openssl for key generation (#678) --- .env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 06d68324..9f857501 100644 --- a/.env.example +++ b/.env.example @@ -42,7 +42,8 @@ MINT_INFO_MOTD="Message to users" MINT_INFO_ICON_URL="https://mint.host/icon.jpg" MINT_INFO_URLS=["https://mint.host", "http://mint8gv0sq5ul602uxt2fe0t80e3c2bi9fy0cxedp69v1vat6ruj81wv.onion"] -MINT_PRIVATE_KEY=supersecretprivatekey +# This is used to derive your mint's private keys. Store it securely. +# MINT_PRIVATE_KEY= # Increment derivation path to rotate to a new keyset # Example: m/0'/0'/0' -> m/0'/0'/1' From 0a230a1aa35d0834f9010bf47635b2132c5d8d9b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:48:39 +0100 Subject: [PATCH 15/19] catch websocket disconenct errors (#674) --- cashu/mint/events/client.py | 5 ++++- cashu/mint/router.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cashu/mint/events/client.py b/cashu/mint/events/client.py index e54c0736..06959fc0 100644 --- a/cashu/mint/events/client.py +++ b/cashu/mint/events/client.py @@ -2,7 +2,7 @@ import json from typing import List, Union -from fastapi import WebSocket +from fastapi import WebSocket, WebSocketDisconnect from loguru import logger from ...core.base import MeltQuote, MintQuote, ProofState @@ -122,6 +122,9 @@ async def start(self): resp = await self._handle_request(req) # Send the response await self._send_msg(resp) + except WebSocketDisconnect as e: + logger.debug(f"Websocket disconnected: {e}") + raise e except Exception as e: err = JSONRPCErrorResponse( error=JSONRPCError( diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 5e7f23c7..abce4c69 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -1,7 +1,7 @@ import asyncio import time -from fastapi import APIRouter, Request, WebSocket +from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect from loguru import logger from ..core.errors import KeysetNotFoundError @@ -204,6 +204,7 @@ async def get_mint_quote(request: Request, quote: str) -> PostMintQuoteResponse: @router.websocket("/v1/ws", name="Websocket endpoint for subscriptions") async def websocket_endpoint(websocket: WebSocket): limit_websocket(websocket) + disconnected = False try: client = ledger.events.add_client(websocket, ledger.db, ledger.crud) except Exception as e: @@ -214,11 +215,16 @@ async def websocket_endpoint(websocket: WebSocket): try: # this will block until the session is closed await client.start() + except WebSocketDisconnect as e: + logger.debug(f"Websocket disconnected: {e}") + disconnected = True + return except Exception as e: logger.debug(f"Exception: {e}") ledger.events.remove_client(client) finally: - await asyncio.wait_for(websocket.close(), timeout=1) + if not disconnected: + await asyncio.wait_for(websocket.close(), timeout=1) @router.post( From 2b233fd67eeca1feda01046d7fe919f84d7ba112 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:55:17 +0100 Subject: [PATCH 16/19] fix: NUT-15 setting remove mpp boolean (#673) * fix: NUT-15 setting remove mpp boolean * remove mpp flag --- cashu/core/models.py | 1 - cashu/mint/features.py | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/cashu/core/models.py b/cashu/core/models.py index 77addfa6..ac11377e 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -73,7 +73,6 @@ def preprocess_deprecated_contact_field(cls, values): class Nut15MppSupport(BaseModel): method: str unit: str - mpp: bool class GetInfoResponse_deprecated(BaseModel): diff --git a/cashu/mint/features.py b/cashu/mint/features.py index f216de9d..18eef1cb 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -67,13 +67,7 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: for method, unit_dict in self.backends.items(): for unit in unit_dict.keys(): if unit_dict[unit].supports_mpp: - mpp_features.append( - { - "method": method.name, - "unit": unit.name, - "mpp": True, - } - ) + mpp_features.append({"method": method.name, "unit": unit.name}) if mpp_features: mint_features[MPP_NUT] = dict(methods=mpp_features) From ee90d840ab81ab722c1e929f6d396ec4ad2ea48f Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:08:46 +0000 Subject: [PATCH 17/19] Add HTTP compression middleware (#676) * Add HTTP compression middleware * Apply fixes from `make format` --- cashu/mint/middleware.py | 50 +++++++- poetry.lock | 250 ++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 3 files changed, 298 insertions(+), 4 deletions(-) diff --git a/cashu/mint/middleware.py b/cashu/mint/middleware.py index f70dbc9c..1fb93e72 100644 --- a/cashu/mint/middleware.py +++ b/cashu/mint/middleware.py @@ -1,12 +1,17 @@ -from fastapi import FastAPI +import gzip +import zlib + +import brotli +import zstandard as zstd +from fastapi import FastAPI, Request, Response from fastapi.exception_handlers import ( request_validation_exception_handler as _request_validation_exception_handler, ) from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from loguru import logger +from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.cors import CORSMiddleware -from starlette.requests import Request from ..core.settings import settings from .limit import _rate_limit_exceeded_handler, limiter_global @@ -26,6 +31,7 @@ def add_middlewares(app: FastAPI): allow_headers=["*"], expose_headers=["*"], ) + app.add_middleware(CompressionMiddleware) if settings.debug_profiling: assert PyInstrumentProfilerMiddleware is not None @@ -53,3 +59,43 @@ async def request_validation_exception_handler( logger.error(detail) # pass on return await _request_validation_exception_handler(request, exc) + + +class CompressionMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + response = await call_next(request) + + # Handle streaming responses differently + if response.__class__.__name__ == 'StreamingResponse': + return response + + response_body = b'' + async for chunk in response.body_iterator: + response_body += chunk + + accept_encoding = request.headers.get("Accept-Encoding", "") + content = response_body + + if "br" in accept_encoding: + content = brotli.compress(content) + response.headers["Content-Encoding"] = "br" + elif "zstd" in accept_encoding: + compressor = zstd.ZstdCompressor() + content = compressor.compress(content) + response.headers["Content-Encoding"] = "zstd" + elif "gzip" in accept_encoding: + content = gzip.compress(content) + response.headers["Content-Encoding"] = "gzip" + elif "deflate" in accept_encoding: + content = zlib.compress(content) + response.headers["Content-Encoding"] = "deflate" + + response.headers["Content-Length"] = str(len(content)) + response.headers["Vary"] = "Accept-Encoding" + + return Response( + content=content, + status_code=response.status_code, + headers=dict(response.headers), + media_type=response.media_type + ) diff --git a/poetry.lock b/poetry.lock index 3a378c8d..156b1fe8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiosqlite" @@ -188,6 +188,140 @@ bitstring = "*" click = "*" coincurve = "*" +[[package]] +name = "brotli" +version = "1.1.0" +description = "Python bindings for the Brotli compression library" +optional = false +python-versions = "*" +files = [ + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, + {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, + {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, + {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, + {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, + {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, + {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, + {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, + {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, + {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, + {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, + {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, + {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, + {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, + {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, + {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, + {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, + {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, +] + [[package]] name = "cbor2" version = "5.6.5" @@ -2190,7 +2324,119 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] +[[package]] +name = "zstandard" +version = "0.23.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, + {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, + {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, + {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, + {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, + {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, + {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, + {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, + {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, + {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, + {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, + {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, + {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, + {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, + {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, +] + +[package.dependencies] +cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} + +[package.extras] +cffi = ["cffi (>=1.11)"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "88baea1edea282ed0bc350ef400254e348995c58dc6fa292aea673ad4068c89d" +content-hash = "b42d5f109c92eb0fe271f9dc3557ca688ebee4d7447df42cff5382d82e4b9f5c" diff --git a/pyproject.toml b/pyproject.toml index 74b84e4b..8c44935b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,8 @@ googleapis-common-protos = "^1.63.2" mypy-protobuf = "^3.6.0" types-protobuf = "^5.27.0.20240626" grpcio-tools = "^1.65.1" +brotli = "^1.1.0" +zstandard = "^0.23.0" [tool.poetry.group.dev.dependencies] pytest-asyncio = "^0.24.0" From 399c20155228a08a5c9b95b1fef91c63d149c8e4 Mon Sep 17 00:00:00 2001 From: lollerfirst <43107113+lollerfirst@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:03:01 +0100 Subject: [PATCH 18/19] NUT-19: Cached Requests and Responses (#624) * fast-api-cache setup * testing the cache * fix * still not working * asynccontextmanager * move test * use redis & custom caching setup (like CDK) * make format * poetry lock * fix format string + log when a cached response is found * log when a cahced response is found * fix tests * poetry lock * try tests on github * use docker compose * maybe we dont need docker * fix types * create_task instead of run * how about we start postgres * mint features * format * remove deprecated setex call * use global expiry for all cached routes * refactor feature map and set default to 1 week * refactor feature construction * Cache NUT-19 --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- .github/workflows/ci.yml | 17 + .github/workflows/tests_redis_cache.yml | 63 ++ .pre-commit-config.yaml | 6 +- cashu/core/nuts.py | 1 + cashu/core/settings.py | 7 + cashu/mint/app.py | 43 +- cashu/mint/cache.py | 67 ++ cashu/mint/features.py | 67 +- cashu/mint/router.py | 7 +- cashu/wallet/helpers.py | 7 +- docker/docker-compose.yml | 8 + poetry.lock | 780 +++++++++++++----------- pyproject.toml | 2 + tests/test_mint_api_cache.py | 120 ++++ 14 files changed, 804 insertions(+), 391 deletions(-) create mode 100644 .github/workflows/tests_redis_cache.yml create mode 100644 cashu/mint/cache.py create mode 100644 docker/docker-compose.yml create mode 100644 tests/test_mint_api_cache.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 784f93eb..e378271d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: jobs: checks: uses: ./.github/workflows/checks.yml + tests: strategy: fail-fast: false @@ -25,6 +26,22 @@ jobs: poetry-version: ${{ matrix.poetry-version }} mint-only-deprecated: ${{ matrix.mint-only-deprecated }} mint-database: ${{ matrix.mint-database }} + + tests_redis_cache: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.10"] + poetry-version: ["1.7.1"] + mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"] + uses: ./.github/workflows/tests_redis_cache.yml + with: + os: ${{ matrix.os }} + python-version: ${{ matrix.python-version }} + poetry-version: ${{ matrix.poetry-version }} + mint-database: ${{ matrix.mint-database }} + regtest: uses: ./.github/workflows/regtest.yml strategy: diff --git a/.github/workflows/tests_redis_cache.yml b/.github/workflows/tests_redis_cache.yml new file mode 100644 index 00000000..f91a7783 --- /dev/null +++ b/.github/workflows/tests_redis_cache.yml @@ -0,0 +1,63 @@ +name: tests_redis_cache + +on: + workflow_call: + inputs: + python-version: + default: "3.10.4" + type: string + poetry-version: + default: "1.7.1" + type: string + mint-database: + default: "" + type: string + os: + default: "ubuntu-latest" + type: string + mint-only-deprecated: + default: "false" + type: string + +jobs: + poetry: + name: Run (db ${{ inputs.mint-database }}, deprecated api ${{ inputs.mint-only-deprecated }}) + runs-on: ${{ inputs.os }} + steps: + - name: Start PostgreSQL service + if: contains(inputs.mint-database, 'postgres') + run: | + docker run -d --name postgres -e POSTGRES_USER=cashu -e POSTGRES_PASSWORD=cashu -e POSTGRES_DB=cashu -p 5432:5432 postgres:latest + until docker exec postgres pg_isready; do sleep 1; done + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Prepare environment + uses: ./.github/actions/prepare + with: + python-version: ${{ inputs.python-version }} + poetry-version: ${{ inputs.poetry-version }} + + - name: Start Redis service + run: | + docker compose -f docker/docker-compose.yml up -d redis + + - name: Run tests + env: + MINT_BACKEND_BOLT11_SAT: FakeWallet + WALLET_NAME: test_wallet + MINT_HOST: localhost + MINT_PORT: 3337 + MINT_TEST_DATABASE: ${{ inputs.mint-database }} + TOR: false + MINT_REDIS_CACHE_ENABLED: true + MINT_REDIS_CACHE_URL: redis://localhost:6379 + run: | + poetry run pytest tests/test_mint_api_cache.py -v --cov=mint --cov-report=xml + + - name: Stop and clean up Docker Compose + run: | + docker compose -f docker/docker-compose.yml down + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1ba1799..24486158 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,4 +22,8 @@ repos: rev: v1.6.0 hooks: - id: mypy - args: [--ignore-missing, --check-untyped-defs] + args: + - --ignore-missing + - --check-untyped-defs + additional_dependencies: + - types-redis diff --git a/cashu/core/nuts.py b/cashu/core/nuts.py index ffe788aa..a2cb062d 100644 --- a/cashu/core/nuts.py +++ b/cashu/core/nuts.py @@ -12,3 +12,4 @@ HTLC_NUT = 14 MPP_NUT = 15 WEBSOCKETS_NUT = 17 +CACHE_NUT = 19 diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 1344f2b2..029363a1 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -231,6 +231,12 @@ class CoreLightningRestFundingSource(MintSettings): mint_corelightning_rest_cert: Optional[str] = Field(default=None) +class MintRedisCache(MintSettings): + mint_redis_cache_enabled: bool = Field(default=False) + mint_redis_cache_url: Optional[str] = Field(default=None) + mint_redis_cache_ttl: Optional[int] = Field(default=60 * 60 * 24 * 7) # 1 week + + class Settings( EnvSettings, LndRPCFundingSource, @@ -240,6 +246,7 @@ class Settings( FakeWalletSettings, MintLimits, MintBackends, + MintRedisCache, MintDeprecationFlags, MintSettings, MintInformation, diff --git a/cashu/mint/app.py b/cashu/mint/app.py index c6e89b7e..16ad1a41 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -1,4 +1,7 @@ +import asyncio import sys +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager from traceback import print_exception from fastapi import FastAPI, status @@ -10,7 +13,7 @@ from ..core.errors import CashuError from ..core.logging import configure_logger from ..core.settings import settings -from .router import router +from .router import redis, router from .router_deprecated import router_deprecated from .startup import shutdown_mint as shutdown_mint_init from .startup import start_mint_init @@ -23,27 +26,35 @@ from .middleware import add_middlewares, request_validation_exception_handler -# this errors with the tests but is the appropriate way to handle startup and shutdown -# until then, we use @app.on_event("startup") -# @asynccontextmanager -# async def lifespan(app: FastAPI): -# # startup routines here -# await start_mint_init() -# yield -# # shutdown routines here + +@asynccontextmanager +async def lifespan(_: FastAPI) -> AsyncIterator[None]: + await start_mint_init() + try: + yield + except asyncio.CancelledError: + # Handle the cancellation gracefully + logger.info("Shutdown process interrupted by CancelledError") + finally: + try: + await redis.disconnect() + await shutdown_mint_init() + except asyncio.CancelledError: + logger.info("CancelledError during shutdown, shutting down forcefully") def create_app(config_object="core.settings") -> FastAPI: configure_logger() app = FastAPI( - title="Nutshell Cashu Mint", - description="Ecash wallet and mint based on the Cashu protocol.", + title="Nutshell Mint", + description="Ecash mint based on the Cashu protocol.", version=settings.version, license_info={ "name": "MIT License", "url": "https://raw.githubusercontent.com/cashubtc/cashu/main/LICENSE", }, + lifespan=lifespan, ) return app @@ -99,13 +110,3 @@ async def catch_exceptions(request: Request, call_next): else: app.include_router(router=router, tags=["Mint"]) app.include_router(router=router_deprecated, tags=["Deprecated"], deprecated=True) - - -@app.on_event("startup") -async def startup_mint(): - await start_mint_init() - - -@app.on_event("shutdown") -async def shutdown_mint(): - await shutdown_mint_init() diff --git a/cashu/mint/cache.py b/cashu/mint/cache.py new file mode 100644 index 00000000..84ebf711 --- /dev/null +++ b/cashu/mint/cache.py @@ -0,0 +1,67 @@ +import asyncio +import functools +import json + +from fastapi import Request +from loguru import logger +from pydantic import BaseModel +from redis.asyncio import from_url +from redis.exceptions import ConnectionError + +from ..core.errors import CashuError +from ..core.settings import settings + + +class RedisCache: + expiry = settings.mint_redis_cache_ttl + + def __init__(self): + if settings.mint_redis_cache_enabled: + if settings.mint_redis_cache_url is None: + raise CashuError("Redis cache url not provided") + self.redis = from_url(settings.mint_redis_cache_url) + asyncio.create_task(self.test_connection()) + + async def test_connection(self): + # PING + try: + await self.redis.ping() + logger.success("Connected to Redis caching server.") + except ConnectionError as e: + logger.error("Redis connection error.") + raise e + + def cache(self): + def passthrough(func): + @functools.wraps(func) + async def wrapper(*args, **kwargs): + logger.trace(f"cache wrapper on route {func.__name__}") + result = await func(*args, **kwargs) + return result + + return wrapper + + def decorator(func): + @functools.wraps(func) + async def wrapper(request: Request, payload: BaseModel): + logger.trace(f"cache wrapper on route {func.__name__}") + key = request.url.path + payload.json() + logger.trace(f"KEY: {key}") + # Check if we have a value under this key + if await self.redis.exists(key): + logger.trace("Returning a cached response...") + resp = await self.redis.get(key) + if resp: + return json.loads(resp) + else: + raise Exception(f"Found no cached response for key {key}") + result = await func(request, payload) + await self.redis.set(name=key, value=result.json(), ex=self.expiry) + return result + + return wrapper + + return passthrough if not settings.mint_redis_cache_enabled else decorator + + async def disconnect(self): + await self.redis.close() diff --git a/cashu/mint/features.py b/cashu/mint/features.py index 18eef1cb..42d9276b 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -6,6 +6,7 @@ MintMethodSetting, ) from ..core.nuts import ( + CACHE_NUT, DLEQ_NUT, FEE_RETURN_NUT, HTLC_NUT, @@ -24,6 +25,15 @@ class LedgerFeatures(SupportsBackends): def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: + mint_features = self.create_mint_features() + mint_features = self.add_supported_features(mint_features) + mint_features = self.add_mpp_features(mint_features) + mint_features = self.add_websocket_features(mint_features) + mint_features = self.add_cache_features(mint_features) + + return mint_features + + def create_mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: mint_method_settings: List[MintMethodSetting] = [] for method, unit_dict in self.backends.items(): for unit in unit_dict.keys(): @@ -42,8 +52,6 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: melt_setting.min_amount = 0 melt_method_settings.append(melt_setting) - supported_dict = dict(supported=True) - mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] = { MINT_NUT: dict( methods=mint_method_settings, @@ -53,15 +61,25 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: methods=melt_method_settings, disabled=False, ), - STATE_NUT: supported_dict, - FEE_RETURN_NUT: supported_dict, - RESTORE_NUT: supported_dict, - SCRIPT_NUT: supported_dict, - P2PK_NUT: supported_dict, - DLEQ_NUT: supported_dict, - HTLC_NUT: supported_dict, } + return mint_features + + def add_supported_features( + self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] + ): + supported_dict = dict(supported=True) + mint_features[STATE_NUT] = supported_dict + mint_features[FEE_RETURN_NUT] = supported_dict + mint_features[RESTORE_NUT] = supported_dict + mint_features[SCRIPT_NUT] = supported_dict + mint_features[P2PK_NUT] = supported_dict + mint_features[DLEQ_NUT] = supported_dict + mint_features[HTLC_NUT] = supported_dict + return mint_features + def add_mpp_features( + self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] + ): # signal which method-unit pairs support MPP mpp_features = [] for method, unit_dict in self.backends.items(): @@ -72,6 +90,11 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: if mpp_features: mint_features[MPP_NUT] = dict(methods=mpp_features) + return mint_features + + def add_websocket_features( + self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] + ): # specify which websocket features are supported # these two are supported by default websocket_features: Dict[str, List[Dict[str, Union[str, List[str]]]]] = { @@ -100,3 +123,29 @@ def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: mint_features[WEBSOCKETS_NUT] = websocket_features return mint_features + + def add_cache_features( + self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] + ): + if settings.mint_redis_cache_enabled: + cache_features: dict[str, list[dict[str, str]] | int] = { + "cached_endpoints": [ + { + "method": "POST", + "path": "/v1/mint/bolt11", + }, + { + "method": "POST", + "path": "/v1/melt/bolt11", + }, + { + "method": "POST", + "path": "/v1/swap", + }, + ] + } + if settings.mint_redis_cache_ttl: + cache_features["ttl"] = settings.mint_redis_cache_ttl + + mint_features[CACHE_NUT] = cache_features + return mint_features diff --git a/cashu/mint/router.py b/cashu/mint/router.py index abce4c69..e66530df 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -28,9 +28,11 @@ ) from ..core.settings import settings from ..mint.startup import ledger +from .cache import RedisCache from .limit import limit_websocket, limiter -router: APIRouter = APIRouter() +router = APIRouter() +redis = RedisCache() @router.get( @@ -237,6 +239,7 @@ async def websocket_endpoint(websocket: WebSocket): ), ) @limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute") +@redis.cache() async def mint( request: Request, payload: PostMintRequest, @@ -314,6 +317,7 @@ async def get_melt_quote(request: Request, quote: str) -> PostMeltQuoteResponse: ), ) @limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute") +@redis.cache() async def melt(request: Request, payload: PostMeltRequest) -> PostMeltQuoteResponse: """ Requests tokens to be destroyed and sent out via Lightning. @@ -336,6 +340,7 @@ async def melt(request: Request, payload: PostMeltRequest) -> PostMeltQuoteRespo ), ) @limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute") +@redis.cache() async def swap( request: Request, payload: PostSwapRequest, diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index fc19f913..ce27ec7b 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -171,10 +171,11 @@ async def send( await wallet.set_reserved(send_proofs, reserved=True) return wallet.available_balance, token + def check_payment_preimage( payment_hash: str, preimage: str, ) -> bool: - return bytes.fromhex(payment_hash) == hashlib.sha256( - bytes.fromhex(preimage) - ).digest() \ No newline at end of file + return ( + bytes.fromhex(payment_hash) == hashlib.sha256(bytes.fromhex(preimage)).digest() + ) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..d380e875 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.8' + +services: + redis: + image: redis:latest + container_name: redis + ports: + - "6379:6379" diff --git a/poetry.lock b/poetry.lock index 156b1fe8..2ece89d0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiosqlite" @@ -53,13 +53,13 @@ files = [ [[package]] name = "async-timeout" -version = "5.0.0" +version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" files = [ - {file = "async_timeout-5.0.0-py3-none-any.whl", hash = "sha256:904719a4bd6e0520047d0ddae220aabee67b877f7ca17bf8cea20f67f6247ae0"}, - {file = "async_timeout-5.0.0.tar.gz", hash = "sha256:49675ec889daacfe65ff66d2dde7dd1447a6f4b2f23721022e4ba121f8772a85"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] @@ -574,73 +574,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.4" +version = "7.6.8" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, - {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, - {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, - {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, - {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, - {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, - {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, - {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, - {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, - {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, - {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, - {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, - {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, - {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, - {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, - {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, + {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, + {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, + {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, + {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, + {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, + {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, + {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, + {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, + {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, + {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, + {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, + {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, + {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, + {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, ] [package.dependencies] @@ -700,20 +700,20 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "deprecated" -version = "1.2.14" +version = "1.2.15" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] [[package]] name = "distlib" @@ -781,13 +781,13 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.115.4" +version = "0.115.5" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"}, - {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"}, + {file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"}, + {file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"}, ] [package.dependencies] @@ -831,13 +831,13 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "googleapis-common-protos" -version = "1.65.0" +version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, ] [package.dependencies] @@ -934,137 +934,137 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.67.1" +version = "1.68.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, - {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, - {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, - {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, - {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, - {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, - {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, - {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, - {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, - {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, - {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, - {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, - {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, - {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, - {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, - {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, - {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, - {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, - {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, - {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, - {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, - {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, - {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, - {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, - {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, + {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, + {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, + {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, + {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, + {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, + {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, + {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, + {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, + {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, + {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, + {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, + {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, + {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, + {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, + {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, + {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, + {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, + {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, + {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, + {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, + {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, + {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, + {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, + {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, + {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.1)"] +protobuf = ["grpcio-tools (>=1.68.0)"] [[package]] name = "grpcio-tools" -version = "1.67.1" +version = "1.68.0" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio_tools-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:c701aaa51fde1f2644bd94941aa94c337adb86f25cd03cf05e37387aaea25800"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:6a722bba714392de2386569c40942566b83725fa5c5450b8910e3832a5379469"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0c7415235cb154e40b5ae90e2a172a0eb8c774b6876f53947cf0af05c983d549"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4c459098c4934f9470280baf9ff8b38c365e147f33c8abc26039a948a664a5"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e89bf53a268f55c16989dab1cf0b32a5bff910762f138136ffad4146129b7a10"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f09cb3e6bcb140f57b878580cf3b848976f67faaf53d850a7da9bfac12437068"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:616dd0c6686212ca90ff899bb37eb774798677e43dc6f78c6954470782d37399"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-win32.whl", hash = "sha256:58a66dbb3f0fef0396737ac09d6571a7f8d96a544ce3ed04c161f3d4fa8d51cc"}, - {file = "grpcio_tools-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:89ee7c505bdf152e67c2cced6055aed4c2d4170f53a2b46a7e543d3b90e7b977"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:6d80ddd87a2fb7131d242f7d720222ef4f0f86f53ec87b0a6198c343d8e4a86e"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b655425b82df51f3bd9fd3ba1a6282d5c9ce1937709f059cb3d419b224532d89"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:250241e6f9d20d0910a46887dfcbf2ec9108efd3b48f3fb95bb42d50d09d03f8"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6008f5a5add0b6f03082edb597acf20d5a9e4e7c55ea1edac8296c19e6a0ec8d"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5eff9818c3831fa23735db1fa39aeff65e790044d0a312260a0c41ae29cc2d9e"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:262ab7c40113f8c3c246e28e369661ddf616a351cb34169b8ba470c9a9c3b56f"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1eebd8c746adf5786fa4c3056258c21cc470e1eca51d3ed23a7fb6a697fe4e81"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-win32.whl", hash = "sha256:3eff92fb8ca1dd55e3af0ef02236c648921fb7d0e8ca206b889585804b3659ae"}, - {file = "grpcio_tools-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ed18281ee17e5e0f9f6ce0c6eb3825ca9b5a0866fc1db2e17fab8aca28b8d9f"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:bd5caef3a484e226d05a3f72b2d69af500dca972cf434bf6b08b150880166f0b"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:48a2d63d1010e5b218e8e758ecb2a8d63c0c6016434e9f973df1c3558917020a"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:baa64a6aa009bffe86309e236c81b02cd4a88c1ebd66f2d92e84e9b97a9ae857"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ab318c40b5e3c097a159035fc3e4ecfbe9b3d2c9de189e55468b2c27639a6ab"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50eba3e31f9ac1149463ad9182a37349850904f142cffbd957cd7f54ec320b8e"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:de6fbc071ecc4fe6e354a7939202191c1f1abffe37fbce9b08e7e9a5b93eba3d"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db9e87f6ea4b0ce99b2651203480585fd9e8dd0dd122a19e46836e93e3a1b749"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-win32.whl", hash = "sha256:6a595a872fb720dde924c4e8200f41d5418dd6baab8cc1a3c1e540f8f4596351"}, - {file = "grpcio_tools-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:92eebb9b31031604ae97ea7657ae2e43149b0394af7117ad7e15894b6cc136dc"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:9a3b9510cc87b6458b05ad49a6dee38df6af37f9ee6aa027aa086537798c3d4a"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e4c9b9fa9b905f15d414cb7bd007ba7499f8907bdd21231ab287a86b27da81a"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:e11a98b41af4bc88b7a738232b8fa0306ad82c79fa5d7090bb607f183a57856f"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de0fcfe61c26679d64b1710746f2891f359593f76894fcf492c37148d5694f00"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae3b3e2ee5aad59dece65a613624c46a84c9582fc3642686537c6dfae8e47dc"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:9a630f83505b6471a3094a7a372a1240de18d0cd3e64f4fbf46b361bac2be65b"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d85a1fcbacd3e08dc2b3d1d46b749351a9a50899fa35cf2ff040e1faf7d405ad"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-win32.whl", hash = "sha256:778470f025f25a1fca5a48c93c0a18af395b46b12dd8df7fca63736b85181f41"}, - {file = "grpcio_tools-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:6961da86e9856b4ddee0bf51ef6636b4bf9c29c0715aa71f3c8f027c45d42654"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:c088dfbbe289bb171ca9c98fabbf7ecc8c1c51af2ba384ef32a4fdcb784b17e9"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11ce546daf8f8c04ee8d4a1673b4754cda4a0a9d505d820efd636e37f46b50c5"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:83fecb2f6119ef0eea68a091964898418c1969375d399956ff8d1741beb7b081"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d39c1aa6b26e2602d815b9cfa37faba48b2889680ae6baa002560cf0f0c69fac"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e975dc9fb61a77d88e739eb17b3361f369d03cc754217f02dd83ec7cfac32e38"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c6e5c5b15f2eedc2a81268d588d14a79a52020383bf87b3c7595df7b571504a"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a974e0ce01806adba718e6eb8c385defe6805b18969b6914da7db55fb055ae45"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-win32.whl", hash = "sha256:35e9b0a82be9f425aa67ee1dc69ba02cf135aeee3f22c0455c5d1b01769bbdb4"}, - {file = "grpcio_tools-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:0436c97f29e654d2eccd7419907ee019caf7eea6bdc6ae91d98011f6c5f44f17"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:718fbb6d68a3d000cb3cf381642660eade0e8c1b0bf7472b84b3367f5b56171d"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:062887d2e9cb8bc261c21a2b8da714092893ce62b4e072775eaa9b24dcbf3b31"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:59dbf14a1ce928bf03a58fa157034374411159ab5d32ad83cf146d9400eed618"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac552fc9c76d50408d7141e1fd1eae69d85fbf7ae71da4d8877eaa07127fbe74"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6583773400e441dc62d08b5a32357babef1a9f9f73c3ac328a75af550815a9"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:862108f90f2f6408908e5ea4584c5104f7caf419c6d73aa3ff36bf8284cca224"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:587c6326425f37dca2291f46b93e446c07ee781cea27725865b806b7a049ec56"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-win32.whl", hash = "sha256:d7d46a4405bd763525215b6e073888386587aef9b4a5ec125bf97ba897ac757d"}, - {file = "grpcio_tools-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:e2fc7980e8bab3ee5ab98b6fdc2a8fbaa4785f196d897531346176fda49a605c"}, - {file = "grpcio_tools-1.67.1.tar.gz", hash = "sha256:d9657f5ddc62b52f58904e6054b7d8a8909ed08a1e28b734be3a707087bcf004"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:9509a5c3ed3d54fa7ac20748d501cb86668f764605a0a68f275339ee0f1dc1a6"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:59a885091bf29700ba0e14a954d156a18714caaa2006a7f328b18e1ac4b1e721"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d3e678162e1d7a8720dc05fdd537fc8df082a50831791f7bb1c6f90095f8368b"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10d03e3ad4af6284fd27cb14f5a3d52045913c1253e3e24a384ed91bc8adbfcd"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1769d7f529de1cc102f7fb900611e3c0b69bdb244fca1075b24d6e5b49024586"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88640d95ee41921ac7352fa5fadca52a06d7e21fbe53e6a706a9a494f756be7d"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e903d07bc65232aa9e7704c829aec263e1e139442608e473d7912417a9908e29"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-win32.whl", hash = "sha256:66b70b37184d40806844f51c2757c6b852511d4ea46a3bf2c7e931a47b455bc6"}, + {file = "grpcio_tools-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:b47ae076ffb29a68e517bc03552bef0d9c973f8e18adadff180b123e973a26ea"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8fefc6d000e169a97336feded23ce614df3fb9926fc48c7a9ff8ea459d93b5b0"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6dd69c9f3ff85eee8d1f71adf7023c638ca8d465633244ac1b7f19bc3668612d"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc5195dc02057668cc22da1ff1aea1811f6fa0deb801b3194dec1fe0bab1cf0"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849b12bec2320e49e988df104c92217d533e01febac172a4495caab36d9f0edc"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:766c2cd2e365e0fc0e559af56f2c2d144d95fd7cb8668a34d533e66d6435eb34"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2ec3a2e0afa4866ccc5ba33c071aebaa619245dfdd840cbb74f2b0591868d085"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-win32.whl", hash = "sha256:80b733014eb40d920d836d782e5cdea0dcc90d251a2ffb35ab378ef4f8a42c14"}, + {file = "grpcio_tools-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:dd9a654af8536b3de8525bff72a245fef62d572eabf96ac946fe850e707cb27d"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0f77957e3a0916a0dd18d57ce6b49d95fc9a5cfed92310f226339c0fda5394f6"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:92a09afe64fe26696595de2036e10967876d26b12c894cc9160f00152cacebe7"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28ebdbad2ef16699d07400b65260240851049a75502eff69a59b127d3ab960f1"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d3150d784d8050b10dcf5eb06e04fb90747a1547fed3a062a608d940fe57066"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:261d98fd635595de42aadee848f9af46da6654d63791c888891e94f66c5d0682"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:061345c0079b9471f32230186ab01acb908ea0e577bc1699a8cf47acef8be4af"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-win32.whl", hash = "sha256:533ce6791a5ba21e35d74c6c25caf4776f5692785a170c01ea1153783ad5af31"}, + {file = "grpcio_tools-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:56842a0ce74b4b92eb62cd5ee00181b2d3acc58ba0c4fd20d15a5db51f891ba6"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:1117a81592542f0c36575082daa6413c57ca39188b18a4c50ec7332616f4b97e"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:51e5a090849b30c99a2396d42140b8a3e558eff6cdfa12603f9582e2cd07724e"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:4fe611d89a1836df8936f066d39c7eb03d4241806449ec45d4b8e1c843ae8011"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c10f3faa0cc4d89eb546f53b623837af23e86dc495d3b89510bcc0e0a6c0b8b2"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46b537480b8fd2195d988120a28467601a2a3de2e504043b89fb90318e1eb754"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:17d0c9004ea82b4213955a585401e80c30d4b37a1d4ace32ccdea8db4d3b7d43"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2919faae04fe47bad57fc9b578aeaab527da260e851f321a253b6b11862254a8"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-win32.whl", hash = "sha256:ee86157ef899f58ba2fe1055cce0d33bd703e99aa6d5a0895581ac3969f06bfa"}, + {file = "grpcio_tools-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:d0470ffc6a93c86cdda48edd428d22e2fef17d854788d60d0d5f291038873157"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:795f2cd76f68a12b0b5541b98187ba367dd69b49d359cf98b781ead742961370"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:57e29e78c33fb1b1d557fbe7650d722d1f2b0a9f53ea73beb8ea47e627b6000b"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:700f171cd3293ee8d50cd43171562ff07b14fa8e49ee471cd91c6924c7da8644"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:196cd8a3a5963a4c9e424314df9eb573b305e6f958fe6508d26580ce01e7aa56"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad40c3164ee9cef62524dea509449ea581b17ea493178beef051bf79b5103ca"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab93fab49fa1e699e577ff5fbb99aba660164d710d4c33cfe0aa9d06f585539f"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:511224a99726eb84db9ddb84dc8a75377c3eae797d835f99e80128ec618376d5"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-win32.whl", hash = "sha256:b4ca81770cd729a9ea536d871aacedbde2b732bb9bb83c9d993d63f58502153d"}, + {file = "grpcio_tools-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:6950725bf7a496f81d3ec3324334ffc9dbec743b510dd0e897f51f8627eeb6ac"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5afd2f3f7257b52228a7808a2b4a765893d4d802d7a2377d9284853e67d045c6"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:453ee3193d59c974c678d91f08786f43c25ef753651b0825dc3d008c31baf68d"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094b22919b786ad73c20372ef5e546330e7cd2c6dc12293b7ed586975f35d38"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26335eea976dfc1ff5d90b19c309a9425bd53868112a0507ad20f297f2c21d3e"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c77ecc5164bb413a613bdac9091dcc29d26834a2ac42fcd1afdfcda9e3003e68"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e31be6dc61496a59c1079b0a669f93dfcc2cdc4b1dbdc4374247cd09cee1329b"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-win32.whl", hash = "sha256:3aa40958355920ae2846c6fb5cadac4f2c8e33234a2982fef8101da0990e3968"}, + {file = "grpcio_tools-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:19bafb80948eda979b1b3a63c1567162d06249f43068a0e46a028a448e6f72d4"}, + {file = "grpcio_tools-1.68.0.tar.gz", hash = "sha256:737804ec2225dd4cc27e633b4ca0e963b0795161bf678285fab6586e917fd867"}, ] [package.dependencies] -grpcio = ">=1.67.1" +grpcio = ">=1.68.0" protobuf = ">=5.26.1,<6.0dev" setuptools = "*" @@ -1081,13 +1081,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.6" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, - {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] @@ -1127,13 +1127,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.6.1" +version = "2.6.2" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3"}, + {file = "identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd"}, ] [package.extras] @@ -1371,13 +1371,13 @@ files = [ [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -1503,54 +1503,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.18" +version = "1.10.19" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, - {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, - {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, - {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, - {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, - {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, - {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, - {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, - {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, - {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, - {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, + {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"}, + {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"}, + {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"}, + {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"}, + {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"}, + {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"}, + {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"}, + {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"}, + {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"}, + {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"}, + {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"}, + {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"}, + {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"}, + {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"}, + {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"}, + {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"}, + {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"}, + {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"}, + {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"}, + {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"}, + {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"}, + {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"}, + {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"}, + {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"}, + {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"}, + {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"}, + {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"}, + {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"}, + {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"}, + {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"}, + {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"}, + {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"}, + {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"}, + {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"}, + {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"}, + {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"}, + {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"}, + {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"}, + {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"}, + {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"}, + {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"}, + {file = "pydantic-1.10.19-py3-none-any.whl", hash = "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"}, + {file = "pydantic-1.10.19.tar.gz", hash = "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"}, ] [package.dependencies] @@ -1771,6 +1771,24 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "redis" +version = "5.2.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.8" +files = [ + {file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"}, + {file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +hiredis = ["hiredis (>=3.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] + [[package]] name = "respx" version = "0.21.1" @@ -1787,29 +1805,29 @@ httpx = ">=0.21.0" [[package]] name = "ruff" -version = "0.7.2" +version = "0.7.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"}, - {file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"}, - {file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"}, - {file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"}, - {file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"}, - {file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"}, - {file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"}, - {file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"}, - {file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"}, - {file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"}, - {file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"}, - {file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"}, + {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, + {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, + {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, + {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, + {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, + {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, + {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, ] [[package]] @@ -1849,23 +1867,23 @@ cffi = ">=1.3.0" [[package]] name = "setuptools" -version = "75.3.0" +version = "75.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, - {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] [[package]] name = "six" @@ -2014,13 +2032,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "starlette" -version = "0.41.2" +version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, - {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, + {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, + {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, ] [package.dependencies] @@ -2031,15 +2049,29 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "tomli" -version = "2.0.2" +version = "2.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] +[[package]] +name = "types-cffi" +version = "1.16.0.20240331" +description = "Typing stubs for cffi" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, + {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, +] + +[package.dependencies] +types-setuptools = "*" + [[package]] name = "types-protobuf" version = "5.28.3.20241030" @@ -2051,6 +2083,47 @@ files = [ {file = "types_protobuf-5.28.3.20241030-py3-none-any.whl", hash = "sha256:f3dae16adf342d4fb5bb3673cabb22549a6252e5dd66fc52d8310b1a39c64ba9"}, ] +[[package]] +name = "types-pyopenssl" +version = "24.1.0.20240722" +description = "Typing stubs for pyOpenSSL" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"}, + {file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-cffi = "*" + +[[package]] +name = "types-redis" +version = "4.6.0.20241004" +description = "Typing stubs for redis" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"}, + {file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-pyOpenSSL = "*" + +[[package]] +name = "types-setuptools" +version = "75.5.0.20241122" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_setuptools-75.5.0.20241122-py3-none-any.whl", hash = "sha256:d69c445f7bdd5e49d1b2441aadcee1388febcc9ad9d9d5fd33648b555e0b1c31"}, + {file = "types_setuptools-75.5.0.20241122.tar.gz", hash = "sha256:196aaf1811cbc1c77ac1d4c4879d5308b6fdf426e56b73baadbca2a1827dadef"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -2228,92 +2301,87 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "wrapt" -version = "1.16.0" +version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, + {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, + {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, + {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, + {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, + {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, + {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, + {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, + {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, + {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, + {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, + {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, + {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, + {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, + {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, + {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, + {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, + {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, + {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, + {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, + {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, + {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, + {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, + {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, ] [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -2439,4 +2507,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b42d5f109c92eb0fe271f9dc3557ca688ebee4d7447df42cff5382d82e4b9f5c" +content-hash = "65ec29cfde181bdb82795dcc479a67d128b1ea787df555bc98e647445f7f5b6b" diff --git a/pyproject.toml b/pyproject.toml index 8c44935b..665fecc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ googleapis-common-protos = "^1.63.2" mypy-protobuf = "^3.6.0" types-protobuf = "^5.27.0.20240626" grpcio-tools = "^1.65.1" +redis = "^5.1.1" brotli = "^1.1.0" zstandard = "^0.23.0" @@ -51,6 +52,7 @@ respx = "^0.21.1" ruff = "^0.7.1" mypy = "^1.13.0" pre-commit = "^4.0.1" +types-redis = "^4.6.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_mint_api_cache.py b/tests/test_mint_api_cache.py new file mode 100644 index 00000000..9e519fe2 --- /dev/null +++ b/tests/test_mint_api_cache.py @@ -0,0 +1,120 @@ +import httpx +import pytest +import pytest_asyncio + +from cashu.core.settings import settings +from cashu.mint.ledger import Ledger +from cashu.wallet.wallet import Wallet +from tests.helpers import pay_if_regtest + +BASE_URL = "http://localhost:3337" +invoice_32sat = "lnbc320n1pnsuamsdqqxqrrsssp5w3tlpw2zss396qh28l3a07u35zdx8nmknzryk89ackn23eywdu2spp5ckt298t835ejzh2xepyxlg57f54q27ffc2zjsjh3t5pmx4wghpcqne0vycw5dfalx5y45d2jtwqfwz437hduyccn9nxk2feay0ytxldjpf3fcjrcf5k2s56q3erj86ymlqdp703y89vt4lr4lun5z5duulcqwuwutn" + + +@pytest_asyncio.fixture(scope="function") +async def wallet(ledger: Ledger): + wallet1 = await Wallet.with_db( + url=BASE_URL, + db="test_data/wallet_mint_api", + name="wallet_mint_api", + ) + await wallet1.load_mint() + yield wallet1 + + +@pytest.mark.asyncio +@pytest.mark.skipif( + not settings.mint_redis_cache_enabled, + reason="settings.mint_redis_cache_enabled is False", +) +async def test_api_mint_cached_responses(wallet: Wallet): + # Testing mint + invoice = await wallet.request_mint(64) + await pay_if_regtest(invoice.request) + + quote_id = invoice.quote + secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10011) + outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + outputs_payload = [o.dict() for o in outputs] + response = httpx.post( + f"{BASE_URL}/v1/mint/bolt11", + json={"quote": quote_id, "outputs": outputs_payload}, + timeout=None, + ) + response1 = httpx.post( + f"{BASE_URL}/v1/mint/bolt11", + json={"quote": quote_id, "outputs": outputs_payload}, + timeout=None, + ) + assert response.status_code == 200, f"{response.status_code = }" + assert response1.status_code == 200, f"{response1.status_code = }" + assert response.text == response1.text + + +@pytest.mark.asyncio +@pytest.mark.skipif( + not settings.mint_redis_cache_enabled, + reason="settings.mint_redis_cache_enabled is False", +) +async def test_api_swap_cached_responses(wallet: Wallet): + quote = await wallet.request_mint(64) + await pay_if_regtest(quote.request) + + minted = await wallet.mint(64, quote.quote) + secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10011) + outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + inputs_payload = [i.dict() for i in minted] + outputs_payload = [o.dict() for o in outputs] + response = httpx.post( + f"{BASE_URL}/v1/swap", + json={"inputs": inputs_payload, "outputs": outputs_payload}, + timeout=None, + ) + response1 = httpx.post( + f"{BASE_URL}/v1/swap", + json={"inputs": inputs_payload, "outputs": outputs_payload}, + timeout=None, + ) + assert response.status_code == 200, f"{response.status_code = }" + assert response1.status_code == 200, f"{response1.status_code = }" + assert response.text == response1.text + + +@pytest.mark.asyncio +@pytest.mark.skipif( + not settings.mint_redis_cache_enabled, + reason="settings.mint_redis_cache_enabled is False", +) +async def test_api_melt_cached_responses(wallet: Wallet): + quote = await wallet.request_mint(64) + melt_quote = await wallet.melt_quote(invoice_32sat) + + await pay_if_regtest(quote.request) + minted = await wallet.mint(64, quote.quote) + + secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10010) + outputs, rs = wallet._construct_outputs([32], secrets, rs) + + inputs_payload = [i.dict() for i in minted] + outputs_payload = [o.dict() for o in outputs] + response = httpx.post( + f"{BASE_URL}/v1/melt/bolt11", + json={ + "quote": melt_quote.quote, + "inputs": inputs_payload, + "outputs": outputs_payload, + }, + timeout=None, + ) + response1 = httpx.post( + f"{BASE_URL}/v1/melt/bolt11", + json={ + "quote": melt_quote.quote, + "inputs": inputs_payload, + "outputs": outputs_payload, + }, + timeout=None, + ) + assert response.status_code == 200, f"{response.status_code = }" + assert response1.status_code == 200, f"{response1.status_code = }" + assert response.text == response1.text From d98d166df19f5684590688645ee143ecad5b2f9d Mon Sep 17 00:00:00 2001 From: lollerfirst <43107113+lollerfirst@users.noreply.github.com> Date: Sun, 15 Dec 2024 00:39:53 +0100 Subject: [PATCH 19/19] Support NUT-XX (signatures on quotes) for mint and wallet side (#670) * nut-19 sign mint quote * ephemeral key for quote * `mint` adjustments + crypto/nut19.py * wip: mint side working * fix import * post-merge fixups * more fixes * make format * move nut19 to nuts directory * `key` -> `privkey` and `pubkey` * make format * mint_info method for nut-19 support * fix tests imports * fix signature missing positional argument + fix db migration format not correctly escaped + pass in NUT-19 keypair to `request_mint` `request_mint_with_callback` * make format * fix `get_invoice_status` * rename to xx * nutxx -> nut20 * mypy * remove `mint_quote_signature_required` as per spec * wip edits * clean up * fix tests * fix deprecated api tests * fix redis tests * fix cache tests * fix regtest mint external * fix mint regtest * add test without signature * test pubkeys in quotes * wip * add compat --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- cashu/core/base.py | 5 + cashu/core/errors.py | 16 +++ cashu/core/models.py | 15 ++- cashu/core/nuts/__init__.py | 0 cashu/core/nuts/nut20.py | 41 ++++++ cashu/core/{ => nuts}/nuts.py | 1 + cashu/core/p2pk.py | 19 --- cashu/mint/crud.py | 5 +- cashu/mint/features.py | 4 +- cashu/mint/ledger.py | 10 +- cashu/mint/migrations.py | 9 ++ cashu/mint/router.py | 6 +- cashu/mint/verification.py | 15 +++ cashu/wallet/cli/cli.py | 25 +++- cashu/wallet/compat.py | 49 +++++++ cashu/wallet/crud.py | 5 +- cashu/wallet/lightning/lightning.py | 5 +- cashu/wallet/migrations.py | 9 ++ cashu/wallet/mint_info.py | 10 +- cashu/wallet/protocols.py | 5 +- cashu/wallet/secrets.py | 44 ++++--- cashu/wallet/transactions.py | 100 +++++++++++++- cashu/wallet/v1_api.py | 24 +++- cashu/wallet/wallet.py | 195 +++++++--------------------- cashu/wallet/wallet_deprecated.py | 1 + tests/test_mint_api.py | 53 +++++++- tests/test_mint_api_cache.py | 34 +++-- tests/test_mint_api_deprecated.py | 12 +- tests/test_mint_operations.py | 33 +++-- tests/test_wallet_subscription.py | 2 +- 30 files changed, 507 insertions(+), 245 deletions(-) create mode 100644 cashu/core/nuts/__init__.py create mode 100644 cashu/core/nuts/nut20.py rename cashu/core/{ => nuts}/nuts.py (88%) create mode 100644 cashu/wallet/compat.py diff --git a/cashu/core/base.py b/cashu/core/base.py index 8922752c..1d047098 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -413,6 +413,8 @@ class MintQuote(LedgerEvent): paid_time: Union[int, None] = None expiry: Optional[int] = None mint: Optional[str] = None + privkey: Optional[str] = None + pubkey: Optional[str] = None @classmethod def from_row(cls, row: Row): @@ -436,6 +438,8 @@ def from_row(cls, row: Row): state=MintQuoteState(row["state"]), created_time=created_time, paid_time=paid_time, + pubkey=row["pubkey"] if "pubkey" in row.keys() else None, + privkey=row["privkey"] if "privkey" in row.keys() else None, ) @classmethod @@ -458,6 +462,7 @@ def from_resp_wallet(cls, mint_quote_resp, mint: str, amount: int, unit: str): mint=mint, expiry=mint_quote_resp.expiry, created_time=int(time.time()), + pubkey=mint_quote_resp.pubkey, ) @property diff --git a/cashu/core/errors.py b/cashu/core/errors.py index 36700acf..13095c6c 100644 --- a/cashu/core/errors.py +++ b/cashu/core/errors.py @@ -96,3 +96,19 @@ class QuoteNotPaidError(CashuError): def __init__(self): super().__init__(self.detail, code=2001) + + +class QuoteSignatureInvalidError(CashuError): + detail = "Signature for mint request invalid" + code = 20008 + + def __init__(self): + super().__init__(self.detail, code=20008) + + +class QuoteRequiresPubkeyError(CashuError): + detail = "Pubkey required for mint quote" + code = 20009 + + def __init__(self): + super().__init__(self.detail, code=20009) diff --git a/cashu/core/models.py b/cashu/core/models.py index ac11377e..e840e4ee 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -128,21 +128,25 @@ class PostMintQuoteRequest(BaseModel): description: Optional[str] = Field( default=None, max_length=settings.mint_max_request_length ) # invoice description + pubkey: Optional[str] = Field( + default=None, max_length=settings.mint_max_request_length + ) # NUT-20 quote lock pubkey class PostMintQuoteResponse(BaseModel): quote: str # quote id request: str # input payment request - paid: Optional[bool] # DEPRECATED as per NUT-04 PR #141 - state: Optional[str] # state of the quote + state: Optional[str] # state of the quote (optional for backwards compat) expiry: Optional[int] # expiry of the quote + pubkey: Optional[str] = None # NUT-20 quote lock pubkey + paid: Optional[bool] = None # DEPRECATED as per NUT-04 PR #141 @classmethod - def from_mint_quote(self, mint_quote: MintQuote) -> "PostMintQuoteResponse": + def from_mint_quote(cls, mint_quote: MintQuote) -> "PostMintQuoteResponse": to_dict = mint_quote.dict() # turn state into string to_dict["state"] = mint_quote.state.value - return PostMintQuoteResponse.parse_obj(to_dict) + return cls.parse_obj(to_dict) # ------- API: MINT ------- @@ -153,6 +157,9 @@ class PostMintRequest(BaseModel): outputs: List[BlindedMessage] = Field( ..., max_items=settings.mint_max_request_length ) + signature: Optional[str] = Field( + default=None, max_length=settings.mint_max_request_length + ) # NUT-20 quote signature class PostMintResponse(BaseModel): diff --git a/cashu/core/nuts/__init__.py b/cashu/core/nuts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cashu/core/nuts/nut20.py b/cashu/core/nuts/nut20.py new file mode 100644 index 00000000..591497b3 --- /dev/null +++ b/cashu/core/nuts/nut20.py @@ -0,0 +1,41 @@ +from hashlib import sha256 +from typing import List + +from ..base import BlindedMessage +from ..crypto.secp import PrivateKey, PublicKey + + +def generate_keypair() -> tuple[str, str]: + privkey = PrivateKey() + assert privkey.pubkey + pubkey = privkey.pubkey + return privkey.serialize(), pubkey.serialize(True).hex() + + +def construct_message(quote_id: str, outputs: List[BlindedMessage]) -> bytes: + serialized_outputs = b"".join([o.B_.encode("utf-8") for o in outputs]) + msgbytes = sha256(quote_id.encode("utf-8") + serialized_outputs).digest() + return msgbytes + + +def sign_mint_quote( + quote_id: str, + outputs: List[BlindedMessage], + private_key: str, +) -> str: + privkey = PrivateKey(bytes.fromhex(private_key), raw=True) + msgbytes = construct_message(quote_id, outputs) + sig = privkey.schnorr_sign(msgbytes, None, raw=True) + return sig.hex() + + +def verify_mint_quote( + quote_id: str, + outputs: List[BlindedMessage], + public_key: str, + signature: str, +) -> bool: + pubkey = PublicKey(bytes.fromhex(public_key), raw=True) + msgbytes = construct_message(quote_id, outputs) + sig = bytes.fromhex(signature) + return pubkey.schnorr_verify(msgbytes, sig, None, raw=True) diff --git a/cashu/core/nuts.py b/cashu/core/nuts/nuts.py similarity index 88% rename from cashu/core/nuts.py rename to cashu/core/nuts/nuts.py index a2cb062d..69c78a0f 100644 --- a/cashu/core/nuts.py +++ b/cashu/core/nuts/nuts.py @@ -13,3 +13,4 @@ MPP_NUT = 15 WEBSOCKETS_NUT = 17 CACHE_NUT = 19 +MINT_QUOTE_SIGNATURE_NUT = 20 diff --git a/cashu/core/p2pk.py b/cashu/core/p2pk.py index 23f379de..eb07a037 100644 --- a/cashu/core/p2pk.py +++ b/cashu/core/p2pk.py @@ -50,22 +50,3 @@ def verify_schnorr_signature( return pubkey.schnorr_verify( hashlib.sha256(message).digest(), signature, None, raw=True ) - - -if __name__ == "__main__": - # generate keys - private_key_bytes = b"12300000000000000000000000000123" - private_key = PrivateKey(private_key_bytes, raw=True) - print(private_key.serialize()) - public_key = private_key.pubkey - assert public_key - print(public_key.serialize().hex()) - - # sign message (=pubkey) - message = public_key.serialize() - signature = private_key.ecdsa_serialize(private_key.ecdsa_sign(message)) - print(signature.hex()) - - # verify - pubkey_verify = PublicKey(message, raw=True) - print(public_key.ecdsa_verify(message, pubkey_verify.ecdsa_deserialize(signature))) diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 0fc53f8c..89feb3c6 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -421,8 +421,8 @@ async def store_mint_quote( await (conn or db).execute( f""" INSERT INTO {db.table_with_schema('mint_quotes')} - (quote, method, request, checking_id, unit, amount, paid, issued, state, created_time, paid_time) - VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :paid, :issued, :state, :created_time, :paid_time) + (quote, method, request, checking_id, unit, amount, paid, issued, state, created_time, paid_time, pubkey) + VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :paid, :issued, :state, :created_time, :paid_time, :pubkey) """, { "quote": quote.quote, @@ -440,6 +440,7 @@ async def store_mint_quote( "paid_time": db.to_timestamp( db.timestamp_from_seconds(quote.paid_time) or "" ), + "pubkey": quote.pubkey or "" }, ) diff --git a/cashu/mint/features.py b/cashu/mint/features.py index 42d9276b..fb9c319b 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -5,13 +5,14 @@ MeltMethodSetting, MintMethodSetting, ) -from ..core.nuts import ( +from ..core.nuts.nuts import ( CACHE_NUT, DLEQ_NUT, FEE_RETURN_NUT, HTLC_NUT, MELT_NUT, MINT_NUT, + MINT_QUOTE_SIGNATURE_NUT, MPP_NUT, P2PK_NUT, RESTORE_NUT, @@ -75,6 +76,7 @@ def add_supported_features( mint_features[P2PK_NUT] = supported_dict mint_features[DLEQ_NUT] = supported_dict mint_features[HTLC_NUT] = supported_dict + mint_features[MINT_QUOTE_SIGNATURE_NUT] = supported_dict return mint_features def add_mpp_features( diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 9161092b..4bac51ed 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -38,6 +38,7 @@ LightningError, NotAllowedError, QuoteNotPaidError, + QuoteSignatureInvalidError, TransactionError, ) from ..core.helpers import sum_proofs @@ -459,6 +460,7 @@ async def mint_quote(self, quote_request: PostMintQuoteRequest) -> MintQuote: state=MintQuoteState.unpaid, created_time=int(time.time()), expiry=expiry, + pubkey=quote_request.pubkey, ) await self.crud.store_mint_quote(quote=quote, db=self.db) await self.events.submit(quote) @@ -518,13 +520,14 @@ async def mint( *, outputs: List[BlindedMessage], quote_id: str, + signature: Optional[str] = None, ) -> List[BlindedSignature]: """Mints new coins if quote with `quote_id` was paid. Ingest blind messages `outputs` and returns blind signatures `promises`. Args: outputs (List[BlindedMessage]): Outputs (blinded messages) to sign. quote_id (str): Mint quote id. - keyset (Optional[MintKeyset], optional): Keyset to use. If not provided, uses active keyset. Defaults to None. + witness (Optional[str], optional): NUT-19 witness signature. Defaults to None. Raises: Exception: Validation of outputs failed. @@ -536,7 +539,6 @@ async def mint( Returns: List[BlindedSignature]: Signatures on the outputs. """ - await self._verify_outputs(outputs) sum_amount_outputs = sum([b.amount for b in outputs]) # we already know from _verify_outputs that all outputs have the same unit because they have the same keyset @@ -549,6 +551,7 @@ async def mint( raise TransactionError("Mint quote already issued.") if not quote.paid: raise QuoteNotPaidError() + previous_state = quote.state await self.db_write._set_mint_quote_pending(quote_id=quote_id) try: @@ -558,6 +561,9 @@ async def mint( raise TransactionError("amount to mint does not match quote amount") if quote.expiry and quote.expiry > int(time.time()): raise TransactionError("quote expired") + if not self._verify_mint_quote_witness(quote, outputs, signature): + raise QuoteSignatureInvalidError() + promises = await self._generate_promises(outputs) except Exception as e: await self.db_write._unset_mint_quote_pending( diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index e4a559cd..39c29487 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -838,3 +838,12 @@ async def m022_quote_set_states_to_values(db: Database): await conn.execute( f"UPDATE {db.table_with_schema('mint_quotes')} SET state = '{mint_quote_states.value}' WHERE state = '{mint_quote_states.name}'" ) + +async def m023_add_key_to_mint_quote_table(db: Database): + async with db.connect() as conn: + await conn.execute( + f""" + ALTER TABLE {db.table_with_schema('mint_quotes')} + ADD COLUMN pubkey TEXT DEFAULT NULL + """ + ) \ No newline at end of file diff --git a/cashu/mint/router.py b/cashu/mint/router.py index e66530df..d1f514da 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -174,6 +174,7 @@ async def mint_quote( paid=quote.paid, # deprecated state=quote.state.value, expiry=quote.expiry, + pubkey=quote.pubkey, ) logger.trace(f"< POST /v1/mint/quote/bolt11: {resp}") return resp @@ -198,6 +199,7 @@ async def get_mint_quote(request: Request, quote: str) -> PostMintQuoteResponse: paid=mint_quote.paid, # deprecated state=mint_quote.state.value, expiry=mint_quote.expiry, + pubkey=mint_quote.pubkey, ) logger.trace(f"< GET /v1/mint/quote/bolt11/{quote}") return resp @@ -251,7 +253,9 @@ async def mint( """ logger.trace(f"> POST /v1/mint/bolt11: {payload}") - promises = await ledger.mint(outputs=payload.outputs, quote_id=payload.quote) + promises = await ledger.mint( + outputs=payload.outputs, quote_id=payload.quote, signature=payload.signature + ) blinded_signatures = PostMintResponse(signatures=promises) logger.trace(f"< POST /v1/mint/bolt11: {blinded_signatures}") return blinded_signatures diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index ca34b7b5..377779e9 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -7,6 +7,7 @@ BlindedSignature, Method, MintKeyset, + MintQuote, Proof, Unit, ) @@ -20,6 +21,7 @@ TransactionError, TransactionUnitError, ) +from ..core.nuts import nut20 from ..core.settings import settings from ..lightning.base import LightningBackend from ..mint.crud import LedgerCrud @@ -277,3 +279,16 @@ def _verify_and_get_unit_method( ) return unit, method + + def _verify_mint_quote_witness( + self, + quote: MintQuote, + outputs: List[BlindedMessage], + signature: Optional[str], + ) -> bool: + """Verify signature on quote id and outputs""" + if not quote.pubkey: + return True + if not signature: + return False + return nut20.verify_mint_quote(quote.quote, outputs, quote.pubkey, signature) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index fef2d45f..824ec807 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -348,7 +348,9 @@ def mint_invoice_callback(msg: JSONRPCNotficationParams): try: asyncio.run( wallet.mint( - int(amount), split=optional_split, quote_id=mint_quote.quote + int(amount), + split=optional_split, + quote_id=mint_quote.quote, ) ) # set paid so we won't react to any more callbacks @@ -402,7 +404,9 @@ def mint_invoice_callback(msg: JSONRPCNotficationParams): mint_quote_resp = await wallet.get_mint_quote(mint_quote.quote) if mint_quote_resp.state == MintQuoteState.paid.value: await wallet.mint( - amount, split=optional_split, quote_id=mint_quote.quote + amount, + split=optional_split, + quote_id=mint_quote.quote, ) paid = True else: @@ -423,7 +427,14 @@ def mint_invoice_callback(msg: JSONRPCNotficationParams): # user paid invoice before and wants to check the quote id elif amount and id: - await wallet.mint(amount, split=optional_split, quote_id=id) + quote = await get_bolt11_mint_quote(wallet.db, quote=id) + if not quote: + raise Exception("Quote not found") + await wallet.mint( + amount, + split=optional_split, + quote_id=quote.quote, + ) # close open subscriptions so we can exit try: @@ -921,11 +932,13 @@ async def invoices(ctx, paid: bool, unpaid: bool, pending: bool, mint: bool): print("No invoices found.") return - async def _try_to_mint_pending_invoice(amount: int, id: str) -> Optional[MintQuote]: + async def _try_to_mint_pending_invoice( + amount: int, quote_id: str + ) -> Optional[MintQuote]: try: - proofs = await wallet.mint(amount, id) + proofs = await wallet.mint(amount, quote_id) print(f"Received {wallet.unit.str(sum_proofs(proofs))}") - return await get_bolt11_mint_quote(db=wallet.db, quote=id) + return await get_bolt11_mint_quote(db=wallet.db, quote=quote_id) except Exception as e: logger.error(f"Could not mint pending invoice: {e}") return None diff --git a/cashu/wallet/compat.py b/cashu/wallet/compat.py new file mode 100644 index 00000000..00590ad2 --- /dev/null +++ b/cashu/wallet/compat.py @@ -0,0 +1,49 @@ +import base64 + +from loguru import logger + +from ..core.crypto.keys import derive_keyset_id +from ..core.db import Database +from ..core.settings import settings +from .crud import ( + get_keysets, + update_keyset, +) +from .protocols import SupportsDb, SupportsMintURL + + +class WalletCompat(SupportsDb, SupportsMintURL): + db: Database + + async def inactivate_base64_keysets(self, force_old_keysets: bool) -> None: + # BEGIN backwards compatibility: phase out keysets with base64 ID by treating them as inactive + if settings.wallet_inactivate_base64_keysets and not force_old_keysets: + keysets_in_db = await get_keysets(mint_url=self.url, db=self.db) + for keyset in keysets_in_db: + if not keyset.active: + continue + # test if the keyset id is a hex string, if not it's base64 + try: + int(keyset.id, 16) + except ValueError: + # verify that it's base64 + try: + _ = base64.b64decode(keyset.id) + except ValueError: + logger.error("Unexpected: keyset id is neither hex nor base64.") + continue + + # verify that we have a hex version of the same keyset by comparing public keys + hex_keyset_id = derive_keyset_id(keys=keyset.public_keys) + if hex_keyset_id not in [k.id for k in keysets_in_db]: + logger.warning( + f"Keyset {keyset.id} is base64 but we don't have a hex version. Ignoring." + ) + continue + + logger.warning( + f"Keyset {keyset.id} is base64 and has a hex counterpart, setting inactive." + ) + keyset.active = False + await update_keyset(keyset=keyset, db=self.db) + # END backwards compatibility diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index 1cf62a2f..75b42d36 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -253,8 +253,8 @@ async def store_bolt11_mint_quote( await (conn or db).execute( """ INSERT INTO bolt11_mint_quotes - (quote, mint, method, request, checking_id, unit, amount, state, created_time, paid_time, expiry) - VALUES (:quote, :mint, :method, :request, :checking_id, :unit, :amount, :state, :created_time, :paid_time, :expiry) + (quote, mint, method, request, checking_id, unit, amount, state, created_time, paid_time, expiry, privkey) + VALUES (:quote, :mint, :method, :request, :checking_id, :unit, :amount, :state, :created_time, :paid_time, :expiry, :privkey) """, { "quote": quote.quote, @@ -268,6 +268,7 @@ async def store_bolt11_mint_quote( "created_time": quote.created_time, "paid_time": quote.paid_time, "expiry": quote.expiry, + "privkey": quote.privkey or "", }, ) diff --git a/cashu/wallet/lightning/lightning.py b/cashu/wallet/lightning/lightning.py index 8f630b9a..176b6e0e 100644 --- a/cashu/wallet/lightning/lightning.py +++ b/cashu/wallet/lightning/lightning.py @@ -107,7 +107,10 @@ async def get_invoice_status(self, request: str) -> PaymentStatus: return PaymentStatus(result=PaymentResult.SETTLED) try: # to check the invoice state, we try minting tokens - await self.mint(mint_quote.amount, quote_id=mint_quote.quote) + await self.mint( + mint_quote.amount, + quote_id=mint_quote.quote, + ) return PaymentStatus(result=PaymentResult.SETTLED) except Exception as e: print(e) diff --git a/cashu/wallet/migrations.py b/cashu/wallet/migrations.py index 8587caf6..af9eff1f 100644 --- a/cashu/wallet/migrations.py +++ b/cashu/wallet/migrations.py @@ -286,3 +286,12 @@ async def m013_add_mint_and_melt_quote_tables(db: Database): ); """ ) + +async def m013_add_key_to_mint_quote_table(db: Database): + async with db.connect() as conn: + await conn.execute( + """ + ALTER TABLE bolt11_mint_quotes + ADD COLUMN privkey TEXT DEFAULT NULL; + """ + ) \ No newline at end of file diff --git a/cashu/wallet/mint_info.py b/cashu/wallet/mint_info.py index a9154c8b..0064981a 100644 --- a/cashu/wallet/mint_info.py +++ b/cashu/wallet/mint_info.py @@ -4,7 +4,7 @@ from ..core.base import Method, Unit from ..core.models import MintInfoContact, Nut15MppSupport -from ..core.nuts import MPP_NUT, WEBSOCKETS_NUT +from ..core.nuts.nuts import MINT_QUOTE_SIGNATURE_NUT, MPP_NUT, WEBSOCKETS_NUT class MintInfo(BaseModel): @@ -53,3 +53,11 @@ def supports_websocket_mint_quote(self, method: Method, unit: Unit) -> bool: if "bolt11_mint_quote" in entry["commands"]: return True return False + + def supports_mint_quote_signature(self) -> bool: + if not self.nuts: + return False + nut20 = self.nuts.get(MINT_QUOTE_SIGNATURE_NUT, None) + if nut20: + return nut20["supported"] + return False diff --git a/cashu/wallet/protocols.py b/cashu/wallet/protocols.py index 1f381a19..8b4a2ced 100644 --- a/cashu/wallet/protocols.py +++ b/cashu/wallet/protocols.py @@ -1,8 +1,8 @@ -from typing import Dict, Protocol +from typing import Dict, List, Protocol import httpx -from ..core.base import Unit, WalletKeyset +from ..core.base import Proof, Unit, WalletKeyset from ..core.crypto.secp import PrivateKey from ..core.db import Database @@ -13,6 +13,7 @@ class SupportsPrivateKey(Protocol): class SupportsDb(Protocol): db: Database + proofs: List[Proof] class SupportsKeysets(Protocol): diff --git a/cashu/wallet/secrets.py b/cashu/wallet/secrets.py index ef35f02f..6bfc8125 100644 --- a/cashu/wallet/secrets.py +++ b/cashu/wallet/secrets.py @@ -156,26 +156,30 @@ async def generate_n_secrets( """ if n < 1: return [], [], [] - - secret_counters_start = await bump_secret_derivation( - db=self.db, keyset_id=self.keyset_id, by=n, skip=skip_bump - ) - logger.trace(f"secret_counters_start: {secret_counters_start}") - secret_counters = list(range(secret_counters_start, secret_counters_start + n)) - logger.trace( - f"Generating secret nr {secret_counters[0]} to {secret_counters[-1]}." - ) - secrets_rs_derivationpaths = [ - await self.generate_determinstic_secret(s) for s in secret_counters - ] - # secrets are supplied as str - secrets = [s[0].hex() for s in secrets_rs_derivationpaths] - # rs are supplied as PrivateKey - rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths] - - derivation_paths = [s[2] for s in secrets_rs_derivationpaths] - - return secrets, rs, derivation_paths + async with self.db.get_connection(lock_table="keysets") as conn: + secret_counters_start = await bump_secret_derivation( + db=self.db, keyset_id=self.keyset_id, by=n, skip=skip_bump, conn=conn + ) + logger.trace(f"secret_counters_start: {secret_counters_start}") + secret_counters = list( + range(secret_counters_start, secret_counters_start + n) + ) + logger.trace( + f"Generating secret nr {secret_counters[0]} to {secret_counters[-1]}." + ) + secrets_rs_derivationpaths = [ + await self.generate_determinstic_secret(s) for s in secret_counters + ] + # secrets are supplied as str + secrets = [s[0].hex() for s in secrets_rs_derivationpaths] + # rs are supplied as PrivateKey + rs = [ + PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths + ] + + derivation_paths = [s[2] for s in secrets_rs_derivationpaths] + + return secrets, rs, derivation_paths async def generate_secrets_from_to( self, from_counter: int, to_counter: int, keyset_id: Optional[str] = None diff --git a/cashu/wallet/transactions.py b/cashu/wallet/transactions.py index 51b0343e..0fb83d68 100644 --- a/cashu/wallet/transactions.py +++ b/cashu/wallet/transactions.py @@ -1,5 +1,5 @@ import uuid -from typing import Dict, List, Union +from typing import Dict, List, Optional, Tuple, Union from loguru import logger @@ -10,6 +10,8 @@ ) from ..core.db import Database from ..core.helpers import amount_summary, sum_proofs +from ..core.settings import settings +from ..core.split import amount_split from ..wallet.crud import ( update_proof, ) @@ -109,6 +111,102 @@ def coinselect_fee(self, proofs: List[Proof], amount: int) -> int: proofs_send = self.coinselect(proofs, amount, include_fees=True) return self.get_fees_for_proofs(proofs_send) + def split_wallet_state(self, amount: int) -> List[int]: + """This function produces an amount split for outputs based on the current state of the wallet. + Its objective is to fill up the wallet so that it reaches `n_target` coins of each amount. + + Args: + amount (int): Amount to split + + Returns: + List[int]: List of amounts to mint + """ + # read the target count for each amount from settings + n_target = settings.wallet_target_amount_count + amounts_we_have = [p.amount for p in self.proofs if p.reserved is not True] + amounts_we_have.sort() + # NOTE: Do not assume 2^n here + all_possible_amounts: list[int] = [2**i for i in range(settings.max_order)] + amounts_we_want_ll = [ + [a] * max(0, n_target - amounts_we_have.count(a)) + for a in all_possible_amounts + ] + # flatten list of lists to list + amounts_we_want = [item for sublist in amounts_we_want_ll for item in sublist] + # sort by increasing amount + amounts_we_want.sort() + + logger.trace( + f"Amounts we have: {[(a, amounts_we_have.count(a)) for a in set(amounts_we_have)]}" + ) + amounts: list[int] = [] + while sum(amounts) < amount and amounts_we_want: + if sum(amounts) + amounts_we_want[0] > amount: + break + amounts.append(amounts_we_want.pop(0)) + + remaining_amount = amount - sum(amounts) + if remaining_amount > 0: + amounts += amount_split(remaining_amount) + amounts.sort() + + logger.trace(f"Amounts we want: {amounts}") + if sum(amounts) != amount: + raise Exception(f"Amounts do not sum to {amount}.") + + return amounts + + def determine_output_amounts( + self, + proofs: List[Proof], + amount: int, + include_fees: bool = False, + keyset_id_outputs: Optional[str] = None, + ) -> Tuple[List[int], List[int]]: + """This function generates a suitable amount split for the outputs to keep and the outputs to send. It + calculates the amount to keep based on the wallet state and the amount to send based on the amount + provided. + + Amount to keep is based on the proofs we have in the wallet + Amount to send is optimally split based on the amount provided plus optionally the fees required to receive them. + + Args: + proofs (List[Proof]): Proofs to be split. + amount (int): Amount to be sent. + include_fees (bool, optional): If True, the fees are included in the amount to send (output of + this method, to be sent in the future). This is not the fee that is required to swap the + `proofs` (input to this method). Defaults to False. + keyset_id_outputs (str, optional): The keyset ID of the outputs to be produced, used to determine the + fee if `include_fees` is set. + + Returns: + Tuple[List[int], List[int]]: Two lists of amounts, one for keeping and one for sending. + """ + # create a suitable amount split based on the proofs provided + total = sum_proofs(proofs) + keep_amt, send_amt = total - amount, amount + + if include_fees: + keyset_id = keyset_id_outputs or self.keyset_id + tmp_proofs = [Proof(id=keyset_id) for _ in amount_split(send_amt)] + fee = self.get_fees_for_proofs(tmp_proofs) + keep_amt -= fee + send_amt += fee + + logger.trace(f"Keep amount: {keep_amt}, send amount: {send_amt}") + logger.trace(f"Total input: {sum_proofs(proofs)}") + # generate optimal split for outputs to send + send_amounts = amount_split(send_amt) + + # we subtract the input fee for the entire transaction from the amount to keep + keep_amt -= self.get_fees_for_proofs(proofs) + logger.trace(f"Keep amount: {keep_amt}") + + # we determine the amounts to keep based on the wallet state + keep_amounts = self.split_wallet_state(keep_amt) + + return keep_amounts, send_amounts + async def set_reserved(self, proofs: List[Proof], reserved: bool) -> None: """Mark a proof as reserved or reset it in the wallet db to avoid reuse when it is sent. diff --git a/cashu/wallet/v1_api.py b/cashu/wallet/v1_api.py index 26bff616..4f4fe61a 100644 --- a/cashu/wallet/v1_api.py +++ b/cashu/wallet/v1_api.py @@ -282,7 +282,11 @@ async def _get_info(self) -> GetInfoResponse: @async_set_httpx_client @async_ensure_mint_loaded async def mint_quote( - self, amount: int, unit: Unit, memo: Optional[str] = None + self, + amount: int, + unit: Unit, + memo: Optional[str] = None, + pubkey: Optional[str] = None, ) -> PostMintQuoteResponse: """Requests a mint quote from the server and returns a payment request. @@ -290,7 +294,7 @@ async def mint_quote( amount (int): Amount of tokens to mint unit (Unit): Unit of the amount memo (Optional[str], optional): Memo to attach to Lightning invoice. Defaults to None. - + pubkey (Optional[str], optional): Public key from which to expect a signature in a subsequent mint request. Returns: PostMintQuoteResponse: Mint Quote Response @@ -298,7 +302,9 @@ async def mint_quote( Exception: If the mint request fails """ logger.trace("Requesting mint: POST /v1/mint/bolt11") - payload = PostMintQuoteRequest(unit=unit.name, amount=amount, description=memo) + payload = PostMintQuoteRequest( + unit=unit.name, amount=amount, description=memo, pubkey=pubkey + ) resp = await self.httpx.post( join(self.url, "/v1/mint/quote/bolt11"), json=payload.dict() ) @@ -333,13 +339,14 @@ async def get_mint_quote(self, quote: str) -> PostMintQuoteResponse: @async_set_httpx_client @async_ensure_mint_loaded async def mint( - self, outputs: List[BlindedMessage], quote: str + self, outputs: List[BlindedMessage], quote: str, signature: Optional[str] = None ) -> List[BlindedSignature]: """Mints new coins and returns a proof of promise. Args: outputs (List[BlindedMessage]): Outputs to mint new tokens with quote (str): Quote ID. + signature (Optional[str], optional): NUT-19 signature of the request. Returns: list[Proof]: List of proofs. @@ -347,16 +354,21 @@ async def mint( Raises: Exception: If the minting fails """ - outputs_payload = PostMintRequest(outputs=outputs, quote=quote) + outputs_payload = PostMintRequest( + outputs=outputs, quote=quote, signature=signature + ) logger.trace("Checking Lightning invoice. POST /v1/mint/bolt11") def _mintrequest_include_fields(outputs: List[BlindedMessage]): """strips away fields from the model that aren't necessary for the /mint""" outputs_include = {"id", "amount", "B_"} - return { + res = { "quote": ..., "outputs": {i: outputs_include for i in range(len(outputs))}, } + if signature: + res["signature"] = ... + return res payload = outputs_payload.dict(include=_mintrequest_include_fields(outputs)) # type: ignore resp = await self.httpx.post( diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index f0a9d76c..ba2020fc 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1,4 +1,3 @@ -import base64 import copy import threading import time @@ -20,7 +19,6 @@ WalletKeyset, ) from ..core.crypto import b_dhke -from ..core.crypto.keys import derive_keyset_id from ..core.crypto.secp import PrivateKey, PublicKey from ..core.db import Database from ..core.errors import KeysetNotFoundError @@ -36,12 +34,14 @@ PostCheckStateResponse, PostMeltQuoteResponse, ) +from ..core.nuts import nut20 from ..core.p2pk import Secret from ..core.settings import settings -from ..core.split import amount_split from . import migrations +from .compat import WalletCompat from .crud import ( bump_secret_derivation, + get_bolt11_mint_quote, get_keysets, get_proofs, invalidate_proof, @@ -68,7 +68,13 @@ class Wallet( - LedgerAPI, WalletP2PK, WalletHTLC, WalletSecrets, WalletTransactions, WalletProofs + LedgerAPI, + WalletP2PK, + WalletHTLC, + WalletSecrets, + WalletTransactions, + WalletProofs, + WalletCompat, ): """ Nutshell wallet class. @@ -248,39 +254,6 @@ async def load_mint_keysets(self, force_old_keysets=False): await self.load_keysets_from_db() - async def inactivate_base64_keysets(self, force_old_keysets: bool) -> None: - # BEGIN backwards compatibility: phase out keysets with base64 ID by treating them as inactive - if settings.wallet_inactivate_base64_keysets and not force_old_keysets: - keysets_in_db = await get_keysets(mint_url=self.url, db=self.db) - for keyset in keysets_in_db: - if not keyset.active: - continue - # test if the keyset id is a hex string, if not it's base64 - try: - int(keyset.id, 16) - except ValueError: - # verify that it's base64 - try: - _ = base64.b64decode(keyset.id) - except ValueError: - logger.error("Unexpected: keyset id is neither hex nor base64.") - continue - - # verify that we have a hex version of the same keyset by comparing public keys - hex_keyset_id = derive_keyset_id(keys=keyset.public_keys) - if hex_keyset_id not in [k.id for k in keysets_in_db]: - logger.warning( - f"Keyset {keyset.id} is base64 but we don't have a hex version. Ignoring." - ) - continue - - logger.warning( - f"Keyset {keyset.id} is base64 and has a hex counterpart, setting inactive." - ) - keyset.active = False - await update_keyset(keyset=keyset, db=self.db) - # END backwards compatibility - async def activate_keyset(self, keyset_id: Optional[str] = None) -> None: """Activates a keyset by setting self.keyset_id. Either activates a specific keyset of chooses one of the active keysets of the mint with the same unit as the wallet. @@ -386,7 +359,10 @@ async def _check_used_secrets(self, secrets): logger.trace("Secret check complete.") async def request_mint_with_callback( - self, amount: int, callback: Callable, memo: Optional[str] = None + self, + amount: int, + callback: Callable, + memo: Optional[str] = None, ) -> Tuple[MintQuote, SubscriptionManager]: """Request a quote invoice for minting tokens. @@ -398,84 +374,56 @@ async def request_mint_with_callback( Returns: MintQuote: Mint Quote """ - mint_qoute = await super().mint_quote(amount, self.unit, memo) + # generate a key for signing the quote request + privkey_hex, pubkey_hex = nut20.generate_keypair() + mint_quote = await super().mint_quote(amount, self.unit, memo, pubkey_hex) subscriptions = SubscriptionManager(self.url) threading.Thread( target=subscriptions.connect, name="SubscriptionManager", daemon=True ).start() subscriptions.subscribe( kind=JSONRPCSubscriptionKinds.BOLT11_MINT_QUOTE, - filters=[mint_qoute.quote], + filters=[mint_quote.quote], callback=callback, ) - quote = MintQuote.from_resp_wallet(mint_qoute, self.url, amount, self.unit.name) + quote = MintQuote.from_resp_wallet(mint_quote, self.url, amount, self.unit.name) + + # store the private key in the quote + quote.privkey = privkey_hex await store_bolt11_mint_quote(db=self.db, quote=quote) return quote, subscriptions - async def request_mint(self, amount: int, memo: Optional[str] = None) -> MintQuote: + async def request_mint( + self, + amount: int, + memo: Optional[str] = None, + ) -> MintQuote: """Request a quote invoice for minting tokens. Args: amount (int): Amount for Lightning invoice in satoshis callback (Optional[Callable], optional): Callback function to be called when the invoice is paid. Defaults to None. memo (Optional[str], optional): Memo for the Lightning invoice. Defaults to None. + keypair (Optional[Tuple[str, str], optional]): NUT-19 private public ephemeral keypair. Defaults to None. Returns: MintQuote: Mint Quote """ - mint_quote_response = await super().mint_quote(amount, self.unit, memo) + # generate a key for signing the quote request + privkey_hex, pubkey_hex = nut20.generate_keypair() + + mint_quote_response = await super().mint_quote( + amount, self.unit, memo, pubkey_hex + ) quote = MintQuote.from_resp_wallet( mint_quote_response, self.url, amount, self.unit.name ) + + quote.privkey = privkey_hex await store_bolt11_mint_quote(db=self.db, quote=quote) return quote - def split_wallet_state(self, amount: int) -> List[int]: - """This function produces an amount split for outputs based on the current state of the wallet. - Its objective is to fill up the wallet so that it reaches `n_target` coins of each amount. - - Args: - amount (int): Amount to split - - Returns: - List[int]: List of amounts to mint - """ - # read the target count for each amount from settings - n_target = settings.wallet_target_amount_count - amounts_we_have = [p.amount for p in self.proofs if p.reserved is not True] - amounts_we_have.sort() - # NOTE: Do not assume 2^n here - all_possible_amounts: list[int] = [2**i for i in range(settings.max_order)] - amounts_we_want_ll = [ - [a] * max(0, n_target - amounts_we_have.count(a)) - for a in all_possible_amounts - ] - # flatten list of lists to list - amounts_we_want = [item for sublist in amounts_we_want_ll for item in sublist] - # sort by increasing amount - amounts_we_want.sort() - - logger.trace( - f"Amounts we have: {[(a, amounts_we_have.count(a)) for a in set(amounts_we_have)]}" - ) - amounts: list[int] = [] - while sum(amounts) < amount and amounts_we_want: - if sum(amounts) + amounts_we_want[0] > amount: - break - amounts.append(amounts_we_want.pop(0)) - - remaining_amount = amount - sum(amounts) - if remaining_amount > 0: - amounts += amount_split(remaining_amount) - amounts.sort() - - logger.trace(f"Amounts we want: {amounts}") - if sum(amounts) != amount: - raise Exception(f"Amounts do not sum to {amount}.") - - return amounts - async def mint( self, amount: int, @@ -509,8 +457,6 @@ async def mint( # split based on our wallet state amounts = split or self.split_wallet_state(amount) - # if no split was specified, we use the canonical split - # amounts = split or amount_split(amount) # quirk: we skip bumping the secret counter in the database since we are # not sure if the minting will succeed. If it succeeds, we will bump it @@ -521,8 +467,15 @@ async def mint( await self._check_used_secrets(secrets) outputs, rs = self._construct_outputs(amounts, secrets, rs) + quote = await get_bolt11_mint_quote(db=self.db, quote=quote_id) + if not quote: + raise Exception("Quote not found.") + signature: str | None = None + if quote.privkey: + signature = nut20.sign_mint_quote(quote_id, outputs, quote.privkey) + # will raise exception if mint is unsuccessful - promises = await super().mint(outputs, quote_id) + promises = await super().mint(outputs, quote_id, signature) promises_keyset_id = promises[0].id await bump_secret_derivation( @@ -547,10 +500,7 @@ async def redeem( self, proofs: List[Proof], ) -> Tuple[List[Proof], List[Proof]]: - """Redeem proofs by sending them to yourself (by calling a split).) - Calls `add_witnesses_to_proofs` which parses all proofs and checks whether their - secrets corresponds to any locks that we have the unlock conditions for. If so, - it adds the unlock conditions to the proofs. + """Redeem proofs by sending them to yourself by calling a split. Args: proofs (List[Proof]): Proofs to be redeemed. """ @@ -558,57 +508,6 @@ async def redeem( self.verify_proofs_dleq(proofs) return await self.split(proofs=proofs, amount=0) - def determine_output_amounts( - self, - proofs: List[Proof], - amount: int, - include_fees: bool = False, - keyset_id_outputs: Optional[str] = None, - ) -> Tuple[List[int], List[int]]: - """This function generates a suitable amount split for the outputs to keep and the outputs to send. It - calculates the amount to keep based on the wallet state and the amount to send based on the amount - provided. - - Amount to keep is based on the proofs we have in the wallet - Amount to send is optimally split based on the amount provided plus optionally the fees required to receive them. - - Args: - proofs (List[Proof]): Proofs to be split. - amount (int): Amount to be sent. - include_fees (bool, optional): If True, the fees are included in the amount to send (output of - this method, to be sent in the future). This is not the fee that is required to swap the - `proofs` (input to this method). Defaults to False. - keyset_id_outputs (str, optional): The keyset ID of the outputs to be produced, used to determine the - fee if `include_fees` is set. - - Returns: - Tuple[List[int], List[int]]: Two lists of amounts, one for keeping and one for sending. - """ - # create a suitable amount split based on the proofs provided - total = sum_proofs(proofs) - keep_amt, send_amt = total - amount, amount - - if include_fees: - keyset_id = keyset_id_outputs or self.keyset_id - tmp_proofs = [Proof(id=keyset_id) for _ in amount_split(send_amt)] - fee = self.get_fees_for_proofs(tmp_proofs) - keep_amt -= fee - send_amt += fee - - logger.trace(f"Keep amount: {keep_amt}, send amount: {send_amt}") - logger.trace(f"Total input: {sum_proofs(proofs)}") - # generate optimal split for outputs to send - send_amounts = amount_split(send_amt) - - # we subtract the input fee for the entire transaction from the amount to keep - keep_amt -= self.get_fees_for_proofs(proofs) - logger.trace(f"Keep amount: {keep_amt}") - - # we determine the amounts to keep based on the wallet state - keep_amounts = self.split_wallet_state(keep_amt) - - return keep_amounts, send_amounts - async def split( self, proofs: List[Proof], @@ -622,6 +521,10 @@ async def split( and the promises to send (send_outputs). If secret_lock is provided, the wallet will create blinded secrets with those to attach a predefined spending condition to the tokens they want to send. + Calls `add_witnesses_to_proofs` which parses all proofs and checks whether their + secrets corresponds to any locks that we have the unlock conditions for. If so, + it adds the unlock conditions to the proofs. + Args: proofs (List[Proof]): Proofs to be split. amount (int): Amount to be sent. diff --git a/cashu/wallet/wallet_deprecated.py b/cashu/wallet/wallet_deprecated.py index 5f555443..a612cade 100644 --- a/cashu/wallet/wallet_deprecated.py +++ b/cashu/wallet/wallet_deprecated.py @@ -261,6 +261,7 @@ async def request_mint_deprecated(self, amount) -> PostMintQuoteResponse: paid=False, state=MintQuoteState.unpaid.value, expiry=decoded_invoice.date + (decoded_invoice.expiry or 0), + pubkey=None ) @async_set_httpx_client diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index 4d9f058f..284fc452 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -14,7 +14,8 @@ PostRestoreRequest, PostRestoreResponse, ) -from cashu.core.nuts import MINT_NUT +from cashu.core.nuts import nut20 +from cashu.core.nuts.nuts import MINT_NUT from cashu.core.settings import settings from cashu.mint.ledger import Ledger from cashu.wallet.crud import bump_secret_derivation @@ -189,12 +190,13 @@ async def test_split(ledger: Ledger, wallet: Wallet): async def test_mint_quote(ledger: Ledger): response = httpx.post( f"{BASE_URL}/v1/mint/quote/bolt11", - json={"unit": "sat", "amount": 100}, + json={"unit": "sat", "amount": 100, "pubkey": "02" + "00" * 32}, ) assert response.status_code == 200, f"{response.url} {response.status_code}" result = response.json() assert result["quote"] assert result["request"] + assert result["pubkey"] == "02" + "00" * 32 # deserialize the response resp_quote = PostMintQuoteResponse(**result) @@ -232,6 +234,7 @@ async def test_mint_quote(ledger: Ledger): # check if DEPRECATED paid flag is also returned assert result2["paid"] is True assert resp_quote.paid is True + assert resp_quote.pubkey == "02" + "00" * 32 @pytest.mark.asyncio @@ -244,10 +247,16 @@ async def test_mint(ledger: Ledger, wallet: Wallet): await pay_if_regtest(mint_quote.request) secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + assert mint_quote.privkey + signature = nut20.sign_mint_quote(mint_quote.quote, outputs, mint_quote.privkey) outputs_payload = [o.dict() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/mint/bolt11", - json={"quote": mint_quote.quote, "outputs": outputs_payload}, + json={ + "quote": mint_quote.quote, + "outputs": outputs_payload, + "signature": signature, + }, timeout=None, ) assert response.status_code == 200, f"{response.url} {response.status_code}" @@ -261,6 +270,44 @@ async def test_mint(ledger: Ledger, wallet: Wallet): assert "s" in result["signatures"][0]["dleq"] +@pytest.mark.asyncio +@pytest.mark.skipif( + settings.debug_mint_only_deprecated, + reason="settings.debug_mint_only_deprecated is set", +) +async def test_mint_bolt11_no_signature(ledger: Ledger, wallet: Wallet): + """ + For backwards compatibility, we do not require a NUT-20 signature + for minting with bolt11. + """ + + response = httpx.post( + f"{BASE_URL}/v1/mint/quote/bolt11", + json={ + "unit": "sat", + "amount": 64, + # no pubkey + }, + ) + assert response.status_code == 200, f"{response.url} {response.status_code}" + result = response.json() + assert result["pubkey"] is None + await pay_if_regtest(result["request"]) + secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001) + outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + outputs_payload = [o.dict() for o in outputs] + response = httpx.post( + f"{BASE_URL}/v1/mint/bolt11", + json={ + "quote": result["quote"], + "outputs": outputs_payload, + # no signature + }, + timeout=None, + ) + assert response.status_code == 200, f"{response.url} {response.status_code}" + + @pytest.mark.asyncio @pytest.mark.skipif( settings.debug_mint_only_deprecated, diff --git a/tests/test_mint_api_cache.py b/tests/test_mint_api_cache.py index 9e519fe2..5776feda 100644 --- a/tests/test_mint_api_cache.py +++ b/tests/test_mint_api_cache.py @@ -2,6 +2,7 @@ import pytest import pytest_asyncio +from cashu.core.nuts import nut20 from cashu.core.settings import settings from cashu.mint.ledger import Ledger from cashu.wallet.wallet import Wallet @@ -29,21 +30,23 @@ async def wallet(ledger: Ledger): ) async def test_api_mint_cached_responses(wallet: Wallet): # Testing mint - invoice = await wallet.request_mint(64) - await pay_if_regtest(invoice.request) + mint_quote = await wallet.request_mint(64) + await pay_if_regtest(mint_quote.request) - quote_id = invoice.quote + quote_id = mint_quote.quote secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10011) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + assert mint_quote.privkey + signature = nut20.sign_mint_quote(quote_id, outputs, mint_quote.privkey) outputs_payload = [o.dict() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/mint/bolt11", - json={"quote": quote_id, "outputs": outputs_payload}, + json={"quote": quote_id, "outputs": outputs_payload, "signature": signature}, timeout=None, ) response1 = httpx.post( f"{BASE_URL}/v1/mint/bolt11", - json={"quote": quote_id, "outputs": outputs_payload}, + json={"quote": quote_id, "outputs": outputs_payload, "signature": signature}, timeout=None, ) assert response.status_code == 200, f"{response.status_code = }" @@ -57,17 +60,23 @@ async def test_api_mint_cached_responses(wallet: Wallet): reason="settings.mint_redis_cache_enabled is False", ) async def test_api_swap_cached_responses(wallet: Wallet): - quote = await wallet.request_mint(64) - await pay_if_regtest(quote.request) + mint_quote = await wallet.request_mint(64) + await pay_if_regtest(mint_quote.request) - minted = await wallet.mint(64, quote.quote) + minted = await wallet.mint(64, mint_quote.quote) secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10011) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) + assert mint_quote.privkey + signature = nut20.sign_mint_quote(mint_quote.quote, outputs, mint_quote.privkey) inputs_payload = [i.dict() for i in minted] outputs_payload = [o.dict() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/swap", - json={"inputs": inputs_payload, "outputs": outputs_payload}, + json={ + "inputs": inputs_payload, + "outputs": outputs_payload, + "signature": signature, + }, timeout=None, ) response1 = httpx.post( @@ -86,15 +95,14 @@ async def test_api_swap_cached_responses(wallet: Wallet): reason="settings.mint_redis_cache_enabled is False", ) async def test_api_melt_cached_responses(wallet: Wallet): - quote = await wallet.request_mint(64) + mint_quote = await wallet.request_mint(64) melt_quote = await wallet.melt_quote(invoice_32sat) - await pay_if_regtest(quote.request) - minted = await wallet.mint(64, quote.quote) + await pay_if_regtest(mint_quote.request) + minted = await wallet.mint(64, mint_quote.quote) secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10010, 10010) outputs, rs = wallet._construct_outputs([32], secrets, rs) - inputs_payload = [i.dict() for i in minted] outputs_payload = [o.dict() for o in outputs] response = httpx.post( diff --git a/tests/test_mint_api_deprecated.py b/tests/test_mint_api_deprecated.py index bf829e76..5426450b 100644 --- a/tests/test_mint_api_deprecated.py +++ b/tests/test_mint_api_deprecated.py @@ -6,6 +6,7 @@ from cashu.core.models import ( CheckSpendableRequest_deprecated, CheckSpendableResponse_deprecated, + GetMintResponse_deprecated, PostRestoreRequest, PostRestoreResponse, ) @@ -124,15 +125,20 @@ async def test_api_mint_validation(ledger): @pytest.mark.asyncio async def test_mint(ledger: Ledger, wallet: Wallet): - mint_quote = await wallet.request_mint(64) - await pay_if_regtest(mint_quote.request) + quote_response = httpx.get( + f"{BASE_URL}/mint", + params={"amount": 64}, + timeout=None, + ) + mint_quote = GetMintResponse_deprecated.parse_obj(quote_response.json()) + await pay_if_regtest(mint_quote.pr) secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) outputs_payload = [o.dict() for o in outputs] response = httpx.post( f"{BASE_URL}/mint", json={"outputs": outputs_payload}, - params={"hash": mint_quote.quote}, + params={"hash": mint_quote.hash}, timeout=None, ) assert response.status_code == 200, f"{response.url} {response.status_code}" diff --git a/tests/test_mint_operations.py b/tests/test_mint_operations.py index 6859e768..ac353ae0 100644 --- a/tests/test_mint_operations.py +++ b/tests/test_mint_operations.py @@ -4,6 +4,7 @@ from cashu.core.base import MeltQuoteState from cashu.core.helpers import sum_proofs from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest +from cashu.core.nuts import nut20 from cashu.core.settings import settings from cashu.mint.ledger import Ledger from cashu.wallet.wallet import Wallet @@ -119,9 +120,9 @@ async def test_melt_external(wallet1: Wallet, ledger: Ledger): @pytest.mark.asyncio @pytest.mark.skipif(is_regtest, reason="only works with FakeWallet") async def test_mint_internal(wallet1: Wallet, ledger: Ledger): - mint_quote = await wallet1.request_mint(128) - await ledger.get_mint_quote(mint_quote.quote) - mint_quote = await ledger.get_mint_quote(mint_quote.quote) + wallet_mint_quote = await wallet1.request_mint(128) + await ledger.get_mint_quote(wallet_mint_quote.quote) + mint_quote = await ledger.get_mint_quote(wallet_mint_quote.quote) assert mint_quote.paid, "mint quote should be paid" @@ -136,7 +137,11 @@ async def test_mint_internal(wallet1: Wallet, ledger: Ledger): len(output_amounts) ) outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs) - await ledger.mint(outputs=outputs, quote_id=mint_quote.quote) + assert wallet_mint_quote.privkey + signature = nut20.sign_mint_quote( + mint_quote.quote, outputs, wallet_mint_quote.privkey + ) + await ledger.mint(outputs=outputs, quote_id=mint_quote.quote, signature=signature) await assert_err( ledger.mint(outputs=outputs, quote_id=mint_quote.quote), @@ -151,9 +156,7 @@ async def test_mint_internal(wallet1: Wallet, ledger: Ledger): @pytest.mark.asyncio @pytest.mark.skipif(is_fake, reason="only works with Regtest") async def test_mint_external(wallet1: Wallet, ledger: Ledger): - quote = await ledger.mint_quote(PostMintQuoteRequest(amount=128, unit="sat")) - assert not quote.paid, "mint quote should not be paid" - assert quote.unpaid + quote = await wallet1.request_mint(128) mint_quote = await ledger.get_mint_quote(quote.quote) assert not mint_quote.paid, "mint quote already paid" @@ -179,7 +182,9 @@ async def test_mint_external(wallet1: Wallet, ledger: Ledger): len(output_amounts) ) outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs) - await ledger.mint(outputs=outputs, quote_id=quote.quote) + assert quote.privkey + signature = nut20.sign_mint_quote(quote.quote, outputs, quote.privkey) + await ledger.mint(outputs=outputs, quote_id=quote.quote, signature=signature) mint_quote_after_payment = await ledger.get_mint_quote(quote.quote) assert mint_quote_after_payment.issued, "mint quote should be issued" @@ -311,14 +316,18 @@ async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger): len(output_amounts) ) outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs) - await ledger.mint(outputs=outputs, quote_id=mint_quote.quote) + assert mint_quote.privkey + signature = nut20.sign_mint_quote(mint_quote.quote, outputs, mint_quote.privkey) + await ledger.mint(outputs=outputs, quote_id=mint_quote.quote, signature=signature) # now try to mint with the same outputs again mint_quote_2 = await wallet1.request_mint(128) await pay_if_regtest(mint_quote_2.request) + assert mint_quote_2.privkey + signature = nut20.sign_mint_quote(mint_quote_2.quote, outputs, mint_quote_2.privkey) await assert_err( - ledger.mint(outputs=outputs, quote_id=mint_quote_2.quote), + ledger.mint(outputs=outputs, quote_id=mint_quote_2.quote, signature=signature), "outputs have already been signed before.", ) @@ -338,7 +347,9 @@ async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger): # we use the outputs once for minting mint_quote_2 = await wallet1.request_mint(128) await pay_if_regtest(mint_quote_2.request) - await ledger.mint(outputs=outputs, quote_id=mint_quote_2.quote) + assert mint_quote_2.privkey + signature = nut20.sign_mint_quote(mint_quote_2.quote, outputs, mint_quote_2.privkey) + await ledger.mint(outputs=outputs, quote_id=mint_quote_2.quote, signature=signature) # use the same outputs for melting mint_quote = await ledger.mint_quote(PostMintQuoteRequest(unit="sat", amount=128)) diff --git a/tests/test_wallet_subscription.py b/tests/test_wallet_subscription.py index 94c26526..2c8dd6d1 100644 --- a/tests/test_wallet_subscription.py +++ b/tests/test_wallet_subscription.py @@ -5,7 +5,7 @@ from cashu.core.base import Method, MintQuoteState, ProofState from cashu.core.json_rpc.base import JSONRPCNotficationParams -from cashu.core.nuts import WEBSOCKETS_NUT +from cashu.core.nuts.nuts import WEBSOCKETS_NUT from cashu.core.settings import settings from cashu.wallet.wallet import Wallet from tests.conftest import SERVER_ENDPOINT