From 676744de84b633fb25df7780fa40e2d5da952647 Mon Sep 17 00:00:00 2001 From: Gyubong Lee Date: Tue, 23 Jan 2024 02:04:30 +0000 Subject: [PATCH] Introduce raftify and RaftContext --- changes/1506.feature.md | 1 + configs/manager/halfstack.toml | 23 +- python.lock | 797 +++++++++--------- requirements.txt | 2 + src/ai/backend/common/distributed.py | 74 +- src/ai/backend/manager/api/context.py | 23 +- src/ai/backend/manager/api/logs.py | 34 +- src/ai/backend/manager/cli/__main__.py | 82 ++ src/ai/backend/manager/config.py | 44 +- src/ai/backend/manager/idle.py | 39 +- src/ai/backend/manager/raft/BUILD | 1 + src/ai/backend/manager/raft/__init__.py | 0 src/ai/backend/manager/raft/logger.py | 41 + src/ai/backend/manager/raft/state_machine.py | 48 ++ src/ai/backend/manager/raft/utils.py | 101 +++ .../backend/manager/scheduler/dispatcher.py | 317 +++---- src/ai/backend/manager/server.py | 116 ++- src/ai/backend/manager/types.py | 9 + tests/common/test_distributed.py | 10 +- tests/manager/test_idle_checker.py | 20 +- tests/manager/test_scheduler.py | 3 + 21 files changed, 1193 insertions(+), 592 deletions(-) create mode 100644 changes/1506.feature.md create mode 100644 src/ai/backend/manager/raft/BUILD create mode 100644 src/ai/backend/manager/raft/__init__.py create mode 100644 src/ai/backend/manager/raft/logger.py create mode 100644 src/ai/backend/manager/raft/state_machine.py create mode 100644 src/ai/backend/manager/raft/utils.py diff --git a/changes/1506.feature.md b/changes/1506.feature.md new file mode 100644 index 00000000000..e5056d80382 --- /dev/null +++ b/changes/1506.feature.md @@ -0,0 +1 @@ +Add Raft-based leader election process to manager group in HA condition in order to make their states consistent. diff --git a/configs/manager/halfstack.toml b/configs/manager/halfstack.toml index 657f39da28e..32203890c70 100644 --- a/configs/manager/halfstack.toml +++ b/configs/manager/halfstack.toml @@ -14,7 +14,7 @@ password = "develove" [manager] -num-proc = 4 +num-proc = 3 service-addr = { host = "0.0.0.0", port = 8081 } #user = "nobody" #group = "nobody" @@ -33,6 +33,27 @@ hide-agents = true # The order of agent selection. agent-selection-resource-priority = ["cuda", "rocm", "tpu", "cpu", "mem"] +[raft] +heartbeat-tick = 3 +election-tick = 10 +log-dir = "./logs" +log-level = "debug" + +[[raft.peers]] +host = "127.0.0.1" +port = 60151 +node-id = 1 + +[[raft.peers]] +host = "127.0.0.1" +port = 60152 +node-id = 2 + +[[raft.peers]] +host = "127.0.0.1" +port = 60153 +node-id = 3 + [docker-registry] ssl-verify = false diff --git a/python.lock b/python.lock index e6d2bf9f06f..ecdac4ade47 100644 --- a/python.lock +++ b/python.lock @@ -72,6 +72,7 @@ // "python-dotenv~=0.20.0", // "python-json-logger>=2.0.1", // "pyzmq~=24.0.1", +// "raftify==0.1.40", // "redis[hiredis]==4.5.5", // "rich~=13.6", // "setproctitle~=1.3.2", @@ -120,19 +121,19 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "1968021eb03b88fcdf5f5398154b21585e941a7b98c9fcef51c4bb0158156619", - "url": "https://files.pythonhosted.org/packages/71/f2/961b67bbe07661d24cfbe3497ba7d5a1c372a50e35d5d05abcefd3057d60/aioconsole-0.6.2-py3-none-any.whl" + "hash": "ee799435f77e8c3a2a7207c465feae2343a2aa537c38e8f56b629c8a321a02d0", + "url": "https://files.pythonhosted.org/packages/f7/39/b392dc1a8bb58342deacc1ed2b00edf88fd357e6fdf76cc6c8046825f84f/aioconsole-0.7.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "bac11286f1062613d2523ceee1ba81c676cd269812b865b66b907448a7b5f63e", - "url": "https://files.pythonhosted.org/packages/5f/14/e5c634fad6a95ffd602fbbd1aa107f05a8ffb79d33ec0d0477f3b137f8a9/aioconsole-0.6.2.tar.gz" + "hash": "c702d24406378d37d9873f91e03ce71520bac503d5ab03f81d8b563ff010bd54", + "url": "https://files.pythonhosted.org/packages/85/da/6a238a72274fa338b2ff20007f026944a6721245fa65d3bd4adeb83be419/aioconsole-0.7.0.tar.gz" } ], "project_name": "aioconsole", "requires_dists": [], - "requires_python": ">=3.7", - "version": "0.6.2" + "requires_python": ">=3.8", + "version": "0.7.0" }, { "artifacts": [ @@ -709,30 +710,31 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", - "url": "https://files.pythonhosted.org/packages/f0/eb/fcb708c7bf5056045e9e98f62b93bd7467eb718b0202e7698eb11d66416c/attrs-23.1.0-py3-none-any.whl" + "hash": "99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1", + "url": "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015", - "url": "https://files.pythonhosted.org/packages/97/90/81f95d5f705be17872843536b1868f351805acf6971251ff07c1b8334dbb/attrs-23.1.0.tar.gz" + "hash": "935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "url": "https://files.pythonhosted.org/packages/e3/fc/f800d51204003fa8ae392c4e8278f256206e7a919b708eef054f5f4b650d/attrs-23.2.0.tar.gz" } ], "project_name": "attrs", "requires_dists": [ - "attrs[docs,tests]; extra == \"dev\"", + "attrs[tests-mypy]; extra == \"tests-no-zope\"", "attrs[tests-no-zope]; extra == \"tests\"", "attrs[tests]; extra == \"cov\"", + "attrs[tests]; extra == \"dev\"", "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", "coverage[toml]>=5.3; extra == \"cov\"", "furo; extra == \"docs\"", "hypothesis; extra == \"tests-no-zope\"", "importlib-metadata; python_version < \"3.8\"", - "mypy>=1.1.1; platform_python_implementation == \"CPython\" and extra == \"tests-no-zope\"", + "mypy>=1.6; (platform_python_implementation == \"CPython\" and python_version >= \"3.8\") and extra == \"tests-mypy\"", "myst-parser; extra == \"docs\"", "pre-commit; extra == \"dev\"", "pympler; extra == \"tests-no-zope\"", - "pytest-mypy-plugins; platform_python_implementation == \"CPython\" and python_version < \"3.11\" and extra == \"tests-no-zope\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.8\") and extra == \"tests-mypy\"", "pytest-xdist[psutil]; extra == \"tests-no-zope\"", "pytest>=4.3.0; extra == \"tests-no-zope\"", "sphinx-notfound-page; extra == \"docs\"", @@ -743,7 +745,7 @@ "zope-interface; extra == \"tests\"" ], "requires_python": ">=3.7", - "version": "23.1.0" + "version": "23.2.0" }, { "artifacts": [ @@ -808,58 +810,98 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "a7a7b8a87e51e5e8ca85b9fdaf3a5dc7aaf123365a09be7a27883d54b9a0c403", - "url": "https://files.pythonhosted.org/packages/36/7c/9fdf669fdc4392496818067861b2ec27c2622df4a355f9257360cb19154a/bcrypt-4.1.1-cp37-abi3-musllinux_1_2_x86_64.whl" + "hash": "69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a", + "url": "https://files.pythonhosted.org/packages/85/23/756228cbc426049c264c86d163ec1b4fb1b06114f26b25fb63132af56126/bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1", + "url": "https://files.pythonhosted.org/packages/05/76/6232380b99d85a2154ae06966b4bf6ce805878a7a92c3211295063b0b6be/bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5", + "url": "https://files.pythonhosted.org/packages/21/d9/7924b194b3aa9bcc39f4592470995841efe71015cb8a79abae9bb043ec28/bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1", + "url": "https://files.pythonhosted.org/packages/22/2e/32c1810b8470aca98c33892fc8c559c1be95eba711cb1bb82fbbf2a4752a/bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326", + "url": "https://files.pythonhosted.org/packages/41/ed/e446078ebe94d8ccac7170ff4bab83d8c86458c6fcfc7c5a4b449974fdd6/bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb", + "url": "https://files.pythonhosted.org/packages/42/9d/a88027b5a8752f4b1831d957470f48e23cebc112aaf762880f3adbfba9cf/bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483", + "url": "https://files.pythonhosted.org/packages/42/c4/13c4bba7e25633b2e94724c642aa93ce376c476d80ecd50d73f0fe2eb38f/bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2", + "url": "https://files.pythonhosted.org/packages/54/fc/fd9a299d4dfd7da38b4570e487ea2465fb92021ab31a08bd66b3caba0baa/bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "d573885b637815a7f3a3cd5f87724d7d0822da64b0ab0aa7f7c78bae534e86dc", - "url": "https://files.pythonhosted.org/packages/03/8e/d69af67a118aaae17a076d41b1b3f4400a66f39900b8cb72a0f918416f65/bcrypt-4.1.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c", + "url": "https://files.pythonhosted.org/packages/5a/5b/dfcd8b7422a8f3b4ce3d28d64307e2f3502e3b5c540dde35eccda2d6c763/bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "f33b385c3e80b5a26b3a5e148e6165f873c1c202423570fdf45fe34e00e5f3e5", - "url": "https://files.pythonhosted.org/packages/1a/cc/ebf49d5d211d1ee622923c9196e6eea1274d1eecc8d00611f8b5f6f1d65a/bcrypt-4.1.1-cp37-abi3-musllinux_1_1_x86_64.whl" + "hash": "eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c", + "url": "https://files.pythonhosted.org/packages/6d/7c/761ab4586beb7aa14b3fa2f382794746a218fffe1d22d9e10926200c8ccd/bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl" }, { "algorithm": "sha256", - "hash": "196008d91201bbb1aa4e666fee5e610face25d532e433a560cabb33bfdff958b", - "url": "https://files.pythonhosted.org/packages/7f/d1/43bca3de2563f3385528e6267aa080ec7097858c3743aec5c2ce39ad7b54/bcrypt-4.1.1-cp37-abi3-macosx_10_12_universal2.whl" + "hash": "33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258", + "url": "https://files.pythonhosted.org/packages/72/07/6a6f2047a9dc9d012b7b977e4041d37d078b76b44b7ee4daf331c1e6fb35/bcrypt-4.1.2.tar.gz" }, { "algorithm": "sha256", - "hash": "755b9d27abcab678e0b8fb4d0abdebeea1f68dd1183b3f518bad8d31fa77d8be", - "url": "https://files.pythonhosted.org/packages/8e/9b/870624ae1deb9cc997b0530fdd45292a6f272f80e024a023d0ea9d5e02e1/bcrypt-4.1.1-cp37-abi3-musllinux_1_2_aarch64.whl" + "hash": "387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc", + "url": "https://files.pythonhosted.org/packages/72/3d/925adb5f5bef7616b504227a431fcaadd9630044802b5c81a31a560b4369/bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "bab33473f973e8058d1b2df8d6e095d237c49fbf7a02b527541a86a5d1dc4444", - "url": "https://files.pythonhosted.org/packages/a0/13/259124a851d361a2549560f9a3ccd286d17ef936017314a58cf7dffce8f7/bcrypt-4.1.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c", + "url": "https://files.pythonhosted.org/packages/88/fd/6025f5530e6ac2513404aa2ab3fb935b9d992dbf24f255f03b5972dace74/bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl" }, { "algorithm": "sha256", - "hash": "12f40f78dcba4aa7d1354d35acf45fae9488862a4fb695c7eeda5ace6aae273f", - "url": "https://files.pythonhosted.org/packages/af/82/96ffdbe0f56b12db0da8f1a9c869399d22231ed1313a84ea2ddc6381a498/bcrypt-4.1.1-cp37-abi3-manylinux_2_28_x86_64.whl" + "hash": "6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966", + "url": "https://files.pythonhosted.org/packages/91/21/6350647549656138a067788d67bdb5ee89ffc2f025618ebf60d3806274c4/bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl" }, { "algorithm": "sha256", - "hash": "fb931cd004a7ad36a89789caf18a54c20287ec1cd62161265344b9c4554fdb2e", - "url": "https://files.pythonhosted.org/packages/c5/8a/e7ba1562bfe80e9c480448f81118ad96087096ac9a36a57674bf8b520d69/bcrypt-4.1.1-cp37-abi3-manylinux_2_28_aarch64.whl" + "hash": "71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63", + "url": "https://files.pythonhosted.org/packages/a4/72/a1276d2fbf5d1af0e29ff9fb5220ce1d49a5f94ccbfb4f9141c963ff9d0e/bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl" }, { "algorithm": "sha256", - "hash": "df37f5418d4f1cdcff845f60e747a015389fa4e63703c918330865e06ad80007", - "url": "https://files.pythonhosted.org/packages/df/56/be5fda8e6fc05123c8c9f526095e93d0802a0a0b2beaf995ee2cc20aa2f8/bcrypt-4.1.1.tar.gz" + "hash": "3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4", + "url": "https://files.pythonhosted.org/packages/ac/c5/243674ec98288af9da31f5b137686746986d5d298dc520e243032160fd1b/bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "2e197534c884336f9020c1f3a8efbaab0aa96fc798068cb2da9c671818b7fbb0", - "url": "https://files.pythonhosted.org/packages/e8/f0/5425ba170098cebff0a0c42b7e8ea8e5c5600fc4344cd058ef0bafc31a3e/bcrypt-4.1.1-cp37-abi3-macosx_13_0_universal2.whl" + "hash": "f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7", + "url": "https://files.pythonhosted.org/packages/b6/1b/1c1cf4efe142dfe6fab912c16766d3eab65b87f33f1d13a08238afce5fdf/bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl" }, { "algorithm": "sha256", - "hash": "2ade10e8613a3b8446214846d3ddbd56cfe9205a7d64742f0b75458c868f7492", - "url": "https://files.pythonhosted.org/packages/ff/c0/da85093fa0babf4fda1e31a1c8aab9026ee9e44539ecf706fe2a4e9391f1/bcrypt-4.1.1-cp37-abi3-musllinux_1_1_aarch64.whl" + "hash": "b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0", + "url": "https://files.pythonhosted.org/packages/bf/26/ec53ccf5cadc81891d53cf0c117cff0f973d98cab6e9d6979578ca5aceeb/bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e", + "url": "https://files.pythonhosted.org/packages/df/cc/5a73c2ecfa9f255423530e8aeaceb0590da12e4c83c99fdac17093f5ce42/bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl" } ], "project_name": "bcrypt", @@ -868,7 +910,7 @@ "pytest!=3.3.0,>=3.2.1; extra == \"tests\"" ], "requires_python": ">=3.7", - "version": "4.1.1" + "version": "4.1.2" }, { "artifacts": [ @@ -898,48 +940,48 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "fcc24f62a1f512dd9b4a7a8af6f5fbfb3d69842a92aa2e79c2ca551ac49a4757", - "url": "https://files.pythonhosted.org/packages/98/ac/bc3123e2a2b5fb99e5632f622a20d6c8c1a7f7ff56fd0f98e77a18cc28b1/boto3-1.33.5-py3-none-any.whl" + "hash": "795803812b78260cfe019ef65021ec9e8049bfb6a027564e1a3b59c1a4a11106", + "url": "https://files.pythonhosted.org/packages/fb/3e/875b653555c79b9c1a35b9b60106fe33ba5ee94489662e9e5f519dce36fa/boto3-1.34.24-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "6a1d938bbf11518b1d17ca8186168f3ba2a0e8b2bf3c82cdd810ecb884627d2a", - "url": "https://files.pythonhosted.org/packages/33/d2/b60aebc8cf6b496a71738a5ac60b5e9b1dc0cb3d207d79cd7fb2af511cf2/boto3-1.33.5.tar.gz" + "hash": "791523f41b5e731c8ac0d2f65b978348fb6c92f02e0dbc9a7bc0b3760195cc60", + "url": "https://files.pythonhosted.org/packages/29/ab/ba7002cfa258519df06a470c6ff7a7a8bfda2e388e4bab1512eb4487580c/boto3-1.34.24.tar.gz" } ], "project_name": "boto3", "requires_dists": [ - "botocore<1.34.0,>=1.33.5", + "botocore<1.35.0,>=1.34.24", "botocore[crt]<2.0a0,>=1.21.0; extra == \"crt\"", "jmespath<2.0.0,>=0.7.1", - "s3transfer<0.9.0,>=0.8.2" + "s3transfer<0.11.0,>=0.10.0" ], - "requires_python": ">=3.7", - "version": "1.33.5" + "requires_python": ">=3.8", + "version": "1.34.24" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "c165207fb33e8352191d6a2770bce9f9bf01c62f5149824c4295d7f49bf96746", - "url": "https://files.pythonhosted.org/packages/dd/e7/ff2caeee2a512ebbdf0552d60a98a192b8006f89888c90a71581cde01c10/botocore-1.33.5-py3-none-any.whl" + "hash": "c92f810b5faec5126f3faf7dc7d77346e407ab3b89bb613290c86ff2fd5405b9", + "url": "https://files.pythonhosted.org/packages/5a/bc/e013db25ff08de5a1eeeb46f9fa14c6e38ad96460e461955a4d3d151ebe7/botocore-1.34.24-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "aa4a5c7cf78a403280e50daba8966479e23577b4a5c20165f71fab7a9b405e99", - "url": "https://files.pythonhosted.org/packages/e8/3c/4e87ed46d4038233454188c37a38f6e81ae307c09e1a45f73e6383acdf42/botocore-1.33.5.tar.gz" + "hash": "4a48c15b87c6a72719a6c2d8688f4e52fe52c18ac9dfcaa25c7e62c5df475ee2", + "url": "https://files.pythonhosted.org/packages/82/4a/31e7791f52702c258fc146eb02185c3c5845aa6cbc5fe8d8e08459758c3a/botocore-1.34.24.tar.gz" } ], "project_name": "botocore", "requires_dists": [ - "awscrt==0.19.17; extra == \"crt\"", + "awscrt==0.19.19; extra == \"crt\"", "jmespath<2.0.0,>=0.7.1", "python-dateutil<3.0.0,>=2.1", "urllib3<1.27,>=1.25.4; python_version < \"3.10\"", "urllib3<2.1,>=1.25.4; python_version >= \"3.10\"" ], - "requires_python": ">=3.7", - "version": "1.33.5" + "requires_python": ">=3.8", + "version": "1.34.24" }, { "artifacts": [ @@ -1402,79 +1444,84 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb", - "url": "https://files.pythonhosted.org/packages/eb/e0/7b484a6e237861ad8e8130bad0f40c8093a79c1d53be0dce822df34d6eb3/frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", + "url": "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", + "url": "https://files.pythonhosted.org/packages/01/bc/8d33f2d84b9368da83e69e42720cff01c5e199b5a868ba4486189a4d8fa9/frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3", - "url": "https://files.pythonhosted.org/packages/1d/29/1a30aedecf5b6542f1dba92383352ccb35a3affcdf94bc5b2917dc95ce3b/frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl" + "hash": "c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", + "url": "https://files.pythonhosted.org/packages/05/08/40159d706a6ed983c8aca51922a93fc69f3c27909e82c537dd4054032674/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472", - "url": "https://files.pythonhosted.org/packages/3f/d9/ea1dd0d8ef6656f49b54be78b5c07fc3bd8929869b1671869cd101a5b421/frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", + "url": "https://files.pythonhosted.org/packages/12/5d/147556b73a53ad4df6da8bbb50715a66ac75c491fdedac3eca8b0b915345/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467", - "url": "https://files.pythonhosted.org/packages/55/7e/aaadd7b18aa939e31a8934c14109d2d673a982be2e7e8c2b02d4311d774b/frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl" + "hash": "fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", + "url": "https://files.pythonhosted.org/packages/5b/9c/f12b69997d3891ddc0d7895999a00b0c6a67f66f79498c0e30f27876435d/frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f", - "url": "https://files.pythonhosted.org/packages/5d/39/3cdbdd3cac07f14d1493dc500463414a0c13cf24d879c9d2a7ff5302dbbb/frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl" + "hash": "fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74", + "url": "https://files.pythonhosted.org/packages/5d/e7/b2469e71f082948066b9382c7b908c22552cc705b960363c390d2e23f587/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01", - "url": "https://files.pythonhosted.org/packages/66/c2/b1535678282812ab1ada7e062d95dfee4604ac5ad1dd0bf4d56ca80c2f96/frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl" + "hash": "442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", + "url": "https://files.pythonhosted.org/packages/83/61/2087bbf24070b66090c0af922685f1d0596c24bb3f3b5223625bdeaf03ca/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251", - "url": "https://files.pythonhosted.org/packages/8c/1f/49c96ccc87127682ba900b092863ef7c20302a2144b3185412a08480ca22/frozenlist-1.4.0.tar.gz" + "hash": "2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", + "url": "https://files.pythonhosted.org/packages/a7/76/180ee1b021568dad5b35b7678616c24519af130ed3fa1e0f1ed4014e0f93/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc", - "url": "https://files.pythonhosted.org/packages/9d/8b/8ab8143541b2c5fff4189fad7853e61d30e4ec4749ebf91e1d598c4e7c57/frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl" + "hash": "1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", + "url": "https://files.pythonhosted.org/packages/a8/be/a235bc937dd803258a370fe21b5aa2dd3e7bfe0287a186a4bec30c6cccd6/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b", - "url": "https://files.pythonhosted.org/packages/a9/fd/c7bb9c6fb5b7bde14285adc84eaaa42d2515a25dc24e583e0ff204383cbc/frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", + "url": "https://files.pythonhosted.org/packages/ac/6e/e0322317b7c600ba21dec224498c0c5959b2bce3865277a7c0badae340a9/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95", - "url": "https://files.pythonhosted.org/packages/af/3b/824653cfd0c25e41ec4f854ddf74b4428d21a7e2683af1801f12d22b3c56/frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl" + "hash": "b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", + "url": "https://files.pythonhosted.org/packages/af/b2/904500d6a162b98a70e510e743e7ea992241b4f9add2c8063bf666ca21df/frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839", - "url": "https://files.pythonhosted.org/packages/b1/4c/9f0762c725f11eab09d9c548e28e0e29b4f8d49848c3bceb6965fe9ecd7c/frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", + "url": "https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c", - "url": "https://files.pythonhosted.org/packages/cc/55/f89d29ac7533eed67a0c5f9aa5aa00cfd82d61c1e863a9a5823feebdc2b3/frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", + "url": "https://files.pythonhosted.org/packages/cf/3d/2102257e7acad73efc4a0c306ad3953f68c504c16982bbdfee3ad75d8085/frozenlist-1.4.1.tar.gz" }, { "algorithm": "sha256", - "hash": "d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f", - "url": "https://files.pythonhosted.org/packages/e4/35/389571a8159875ab238ef98f1f7b1ea8b98e084ffbfd2c4c0a5cf4dc9187/frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", + "url": "https://files.pythonhosted.org/packages/db/1b/6a5b970e55dffc1a7d0bb54f57b184b2a2a2ad0b7bca16a97ca26d73c5b5/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b", - "url": "https://files.pythonhosted.org/packages/e6/7e/74b176a5580e1a41da326d07cf47a0032923fb3eeec9afbd92bb5c6457df/frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", + "url": "https://files.pythonhosted.org/packages/e0/18/9f09f84934c2b2aa37d539a322267939770362d5495f37783440ca9c1b74/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" } ], "project_name": "frozenlist", "requires_dists": [], "requires_python": ">=3.8", - "version": "1.4.0" + "version": "1.4.1" }, { "artifacts": [ @@ -1493,13 +1540,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "9b82d5c8d3479a5391ea0a46d81cca698d328459da31d4a459d4e901a5d927e0", - "url": "https://files.pythonhosted.org/packages/ca/7e/2d41727aeba37b84e1ca515fbb2ca0d706c591ca946236466ffe575b2059/google_auth-2.24.0-py2.py3-none-any.whl" + "hash": "3f445c8ce9b61ed6459aad86d8ccdba4a9afed841b2d1451a11ef4db08957424", + "url": "https://files.pythonhosted.org/packages/aa/42/c3873f5a4369d28eb0006bfc80837f28b18bd4e04526f55cc9c8eac7a803/google_auth-2.26.2-py2.py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "2ec7b2a506989d7dbfdbe81cb8d0ead8876caaed14f86d29d34483cbe99c57af", - "url": "https://files.pythonhosted.org/packages/54/62/a13a5cac653075436ef97fc1c7b48eba277c5bfa3c8613617a733a832cbc/google-auth-2.24.0.tar.gz" + "hash": "97327dbbf58cccb58fc5a1712bba403ae76668e64814eb30f7316f7e27126b81", + "url": "https://files.pythonhosted.org/packages/9a/a7/96f6b41c736ac080844a96d34896019127427e66f59d6b03e001d243c6c6/google-auth-2.26.2.tar.gz" } ], "project_name": "google-auth", @@ -1517,7 +1564,7 @@ "rsa<5,>=3.1.4" ], "requires_python": ">=3.7", - "version": "2.24.0" + "version": "2.26.2" }, { "artifacts": [ @@ -1608,58 +1655,59 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94", - "url": "https://files.pythonhosted.org/packages/ce/76/257d50829841cb13b163764cdef35197c8a0bd351ad94fc05795ca28fb21/greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", + "url": "https://files.pythonhosted.org/packages/94/ed/1e5f4bca691a81700e5a88e86d6f0e538acb10188cd2cc17140e523255ef/greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65", - "url": "https://files.pythonhosted.org/packages/3b/20/da6746e1efbb114740b6e1671ee0d35a5ff39e49f6a1c169e8328d47b7c8/greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl" + "hash": "43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", + "url": "https://files.pythonhosted.org/packages/17/14/3bddb1298b9a6786539ac609ba4b7c9c0842e12aa73aaa4d8d73ec8f8185/greenlet-3.0.3.tar.gz" }, { "algorithm": "sha256", - "hash": "19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec", - "url": "https://files.pythonhosted.org/packages/3e/87/88d45172c2fe19052d782bf616ce5a2a92604823320b7cd59ea2dd9ad41d/greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", + "url": "https://files.pythonhosted.org/packages/21/b4/90e06e07c78513ab03855768200bdb35c8e764e805b3f14fb488e56f82dc/greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96", - "url": "https://files.pythonhosted.org/packages/42/85/32e38abd5f046d56c9ff762c66ddd763cee17daccefa6f22fdae7f7e6472/greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33", + "url": "https://files.pythonhosted.org/packages/42/11/42ad6b1104c357826bbee7d7b9e4f24dbd9fde94899a03efb004aab62963/greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b", - "url": "https://files.pythonhosted.org/packages/54/df/718c9b3e90edba70fa919bb3aaa5c3c8dabf3a8252ad1e93d33c348e5ca4/greenlet-3.0.1.tar.gz" + "hash": "b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", + "url": "https://files.pythonhosted.org/packages/6e/20/68a278a6f93fa36e21cfc3d7599399a8a831225644eb3b6b18755cd3d6fc/greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl" }, { "algorithm": "sha256", - "hash": "2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234", - "url": "https://files.pythonhosted.org/packages/5b/ee/3b61723db7690e1168f4ed1af98ea595bcc843c6221d13846d6cc390b2cb/greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl" + "hash": "2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", + "url": "https://files.pythonhosted.org/packages/bb/6b/384dee7e0121cbd1757bdc1824a5ee28e43d8d4e3f99aa59521f629442fe/greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884", - "url": "https://files.pythonhosted.org/packages/6b/bd/033343cf60d27702d3be9edba9dbc8392594e6c4a6eede337dbb40e0c4b2/greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", + "url": "https://files.pythonhosted.org/packages/c6/1f/12d5a6cc26e8b483c2e7975f9c22e088ac735c0d8dcb8a8f72d31a4e5f04/greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl" }, { "algorithm": "sha256", - "hash": "1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a", - "url": "https://files.pythonhosted.org/packages/b1/62/1501a7dd0ac305a3f2c4d5ac9e526a71e96070cb1c27a6d2d7fd11c65d38/greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", + "url": "https://files.pythonhosted.org/packages/c7/ec/85b647e59e0f137c7792a809156f413e38379cf7f3f2e1353c37f4be4026/greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72", - "url": "https://files.pythonhosted.org/packages/b7/c1/bf937378fd918599a3b51f55bf049e5df59eac6557380a30f3e78da56b7e/greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", + "url": "https://files.pythonhosted.org/packages/f6/a2/0ed21078039072f9dc738bbf3af12b103a84106b1385ac4723841f846ce7/greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" } ], "project_name": "greenlet", "requires_dists": [ "Sphinx; extra == \"docs\"", + "furo; extra == \"docs\"", "objgraph; extra == \"test\"", "psutil; extra == \"test\"" ], "requires_python": ">=3.7", - "version": "3.0.1" + "version": "3.0.3" }, { "artifacts": [ @@ -1767,79 +1815,79 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "071c5814b850574036506a8118034f97c3cbf2fe9947ff45a27b07a48da56240", - "url": "https://files.pythonhosted.org/packages/05/0a/8e5ae10854b1d67f8cd9e1b187e1128bde911f6b44ac6db4ad714514ef27/hiredis-2.2.3-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "7bac7e02915b970c3723a7a7c5df4ba7a11a3426d2a3f181e041aa506a1ff028", + "url": "https://files.pythonhosted.org/packages/77/8d/7a0b1e463073f987a2d269af687877a295dde4daad3842f2731d92564888/hiredis-2.3.2-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "4e43e2b5acaad09cf48c032f7e4926392bb3a3f01854416cf6d82ebff94d5467", - "url": "https://files.pythonhosted.org/packages/03/f3/e34dbb46f8f0d1a0e6c6aecf082c22de7a683df02c2bc29f74f9ce680eb0/hiredis-2.2.3-cp311-cp311-musllinux_1_1_i686.whl" + "hash": "0da56915bda1e0a49157191b54d3e27689b70960f0685fdd5c415dacdee2fbed", + "url": "https://files.pythonhosted.org/packages/0f/81/1a87b64a4c33e18c30b4e41d8a4cf222c2fff74933d27ed293c2465a6439/hiredis-2.3.2-cp311-cp311-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "2fb9300959a0048138791f3d68359d61a788574ec9556bddf1fec07f2dbc5320", - "url": "https://files.pythonhosted.org/packages/12/c6/cb1f5bccabf3cc05a1ee56978177a2dcc444e19d743ec95adfcfbb2a1405/hiredis-2.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + "hash": "28bd184b33e0dd6d65816c16521a4ba1ffbe9ff07d66873c42ea4049a62fed83", + "url": "https://files.pythonhosted.org/packages/2e/a1/e149bbe353c202eb578f8f13426fa9e8a9bedb72e6afd1947d830890db3f/hiredis-2.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "126623b03c31cb6ac3e0d138feb6fcc36dd43dd34fc7da7b7a0c38b5d75bc896", - "url": "https://files.pythonhosted.org/packages/1b/cc/14789efd9707a1d799c9cb1fbac630044e5cec2764eca4bd28aa60dda80a/hiredis-2.2.3-cp311-cp311-musllinux_1_1_s390x.whl" + "hash": "e2674a5a3168349435b08fa0b82998ed2536eb9acccf7087efe26e4cd088a525", + "url": "https://files.pythonhosted.org/packages/49/c0/35cf55026829e06b9c3e9927b7f66f1310afc1f08087796462b02c27eaef/hiredis-2.3.2-cp311-cp311-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "3f5446068197b35a11ccc697720c41879c8657e2e761aaa8311783aac84cef20", - "url": "https://files.pythonhosted.org/packages/29/e5/b6022797834c9e935ff814dcf96d03abea2b669e8359be69a7ed96e36a57/hiredis-2.2.3-cp311-cp311-macosx_11_0_arm64.whl" + "hash": "dfa904045d7cebfb0f01dad51352551cce1d873d7c3f80c7ded7d42f8cac8f89", + "url": "https://files.pythonhosted.org/packages/4b/d3/b99f6bc21971a7ee889711e62fa9444012999821b06d4e0db300195ef17e/hiredis-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "7df645b6b7800e8b748c217fbd6a4ca8361bcb9a1ae6206cc02377833ec8a1aa", - "url": "https://files.pythonhosted.org/packages/67/b9/513511d1ceaf2e519f202a0aecfacc3d3d8eeea8042946e96f8955cbcaa0/hiredis-2.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + "hash": "14c7b43205e515f538a9defb4e411e0f0576caaeeda76bb9993ed505486f7562", + "url": "https://files.pythonhosted.org/packages/6c/6a/d8e9c48487e6f0e39fa921d88d04fef7e96500e17c5b7eb0c5bcf9406c1f/hiredis-2.3.2-cp311-cp311-musllinux_1_1_s390x.whl" }, { "algorithm": "sha256", - "hash": "8eceffca3941775b646cd585cd19b275d382de43cc3327d22f7c75d7b003d481", - "url": "https://files.pythonhosted.org/packages/76/98/3b99c70981d4e882c65bcfe5a0b3b26afc6da5d4a3c51f964e20ee0b41d6/hiredis-2.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "bd40d2e2f82a483de0d0a6dfd8c3895a02e55e5c9949610ecbded18188fd0a56", + "url": "https://files.pythonhosted.org/packages/6f/89/2bbc2d9400dbb18161e9e6ce5821ff06725a78df2e471875a667f6b61d08/hiredis-2.3.2-cp311-cp311-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "a7205497d7276a81fe92951a29616ef96562ed2f91a02066f72b6f93cb34b40e", - "url": "https://files.pythonhosted.org/packages/88/43/14d2ab141cc10a30fd8dcb00addfc38c0bb7911165314b0b5dca493c4033/hiredis-2.2.3-cp311-cp311-musllinux_1_1_ppc64le.whl" + "hash": "adfbf2e9c38b77d0db2fb32c3bdaea638fa76b4e75847283cd707521ad2475ef", + "url": "https://files.pythonhosted.org/packages/83/94/6e729760c66b3eeeec1ea02e06596d7a36b97fa414dd7e25130a34be5238/hiredis-2.3.2-cp311-cp311-macosx_10_15_universal2.whl" }, { "algorithm": "sha256", - "hash": "b17baf702c6e5b4bb66e1281a3efbb1d749c9d06cdb92b665ad81e03118f78fc", - "url": "https://files.pythonhosted.org/packages/98/a5/6c0297dceb719ff2d9412072ec86673e1eea12c9e23e1215279dc59f4a1f/hiredis-2.2.3-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "f70481213373d44614148f0f2e38e7905be3f021902ae5167289413196de4ba4", + "url": "https://files.pythonhosted.org/packages/a2/3d/1b6a97fd60998206a09b7c9426d83f1401cd63e358ba385800d2ffad7174/hiredis-2.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, { "algorithm": "sha256", - "hash": "2d7e459fe7313925f395148d36d9b7f4f8dac65be06e45d7af356b187cef65fc", - "url": "https://files.pythonhosted.org/packages/a1/ce/641fdcfc7d6003a1d2ada35dd5741d9aac9b4e94b15515c9ea33db1b32dc/hiredis-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "02fc71c8333586871602db4774d3a3e403b4ccf6446dc4603ec12df563127cee", + "url": "https://files.pythonhosted.org/packages/b3/01/f71c0ca9acfb2ce6eacb8bfa9963a19220fdfce4b7bc0c6af247ffdec6ec/hiredis-2.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "e75163773a309e56a9b58165cf5a50e0f84b755f6ff863b2c01a38918fe92daa", - "url": "https://files.pythonhosted.org/packages/b0/04/dab6792584fc548803ffa50b5bb2b99f01d3ab04d7c7f64e85f1a22fb847/hiredis-2.2.3.tar.gz" + "hash": "eb8797b528c1ff81eef06713623562b36db3dafa106b59f83a6468df788ff0d1", + "url": "https://files.pythonhosted.org/packages/cd/24/86f9359ec4de465bafc90890b57439eca907882d03602a08eb3abec05a68/hiredis-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "4c3b8be557e08b234774925622e196f0ee36fe4eab66cd19df934d3efd8f3743", - "url": "https://files.pythonhosted.org/packages/b1/80/f018c72cf5100da89b47b62ba6f14086bacd0eff58c4e112ee215ce7faeb/hiredis-2.2.3-cp311-cp311-macosx_10_12_x86_64.whl" + "hash": "80b02d27864ebaf9b153d4b99015342382eeaed651f5591ce6f07e840307c56d", + "url": "https://files.pythonhosted.org/packages/d9/08/e1de65a655ad7ab3b76d01e718a5f4e3503d8c25bf107731608e49a859df/hiredis-2.3.2-cp311-cp311-macosx_10_15_x86_64.whl" }, { "algorithm": "sha256", - "hash": "d115790f18daa99b5c11a506e48923b630ef712e9e4b40482af942c3d40638b8", - "url": "https://files.pythonhosted.org/packages/d7/7a/a8855c545512ea07cae0c859983a6fce9a82e6b321889e5c2e1dcb50eb7e/hiredis-2.2.3-cp311-cp311-macosx_10_12_universal2.whl" + "hash": "dc1c3fd49930494a67dcec37d0558d99d84eca8eb3f03b17198424538f2608d7", + "url": "https://files.pythonhosted.org/packages/dd/15/c582c84db3854275d80786cf4e1a01ef11fd70e016a083707a9e24bdd36a/hiredis-2.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl" }, { "algorithm": "sha256", - "hash": "aa17a3b22b3726d54d7af20394f65d4a1735a842a4e0f557dc67a90f6965c4bc", - "url": "https://files.pythonhosted.org/packages/ea/6d/4d230772220486fb7fa0991d3d36c8592c3d941f765b1aea8cf1d5468bee/hiredis-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "733e2456b68f3f126ddaf2cd500a33b25146c3676b97ea843665717bda0c5d43", + "url": "https://files.pythonhosted.org/packages/fe/2d/a5ae61da1157644f7e52e088fa158ac6f5d09775112d14b1c9b9a5156bf1/hiredis-2.3.2.tar.gz" } ], "project_name": "hiredis", "requires_dists": [], "requires_python": ">=3.7", - "version": "2.2.3" + "version": "2.3.2" }, { "artifacts": [ @@ -1921,46 +1969,6 @@ "requires_python": null, "version": "0.2.0" }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", - "url": "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743", - "url": "https://files.pythonhosted.org/packages/33/44/ae06b446b8d8263d712a211e959212083a5eda2bf36d57ca7415e03f6f36/importlib_metadata-6.8.0.tar.gz" - } - ], - "project_name": "importlib-metadata", - "requires_dists": [ - "flufl.flake8; extra == \"testing\"", - "furo; extra == \"docs\"", - "importlib-resources>=1.3; python_version < \"3.9\" and extra == \"testing\"", - "ipython; extra == \"perf\"", - "jaraco.packaging>=9; extra == \"docs\"", - "jaraco.tidelift>=1.4; extra == \"docs\"", - "packaging; extra == \"testing\"", - "pyfakefs; extra == \"testing\"", - "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", - "pytest-checkdocs>=2.4; extra == \"testing\"", - "pytest-cov; extra == \"testing\"", - "pytest-enabler>=2.2; extra == \"testing\"", - "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", - "pytest-perf>=0.9.2; extra == \"testing\"", - "pytest-ruff; extra == \"testing\"", - "pytest>=6; extra == \"testing\"", - "rst.linker>=1.9; extra == \"docs\"", - "sphinx-lint; extra == \"docs\"", - "sphinx>=3.5; extra == \"docs\"", - "typing-extensions>=3.6.4; python_version < \"3.8\"", - "zipp>=0.5" - ], - "requires_python": ">=3.8", - "version": "6.8.0" - }, { "artifacts": [ { @@ -2025,13 +2033,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - "url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl" + "hash": "7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", + "url": "https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "url": "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz" + "hash": "ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90", + "url": "https://files.pythonhosted.org/packages/b2/5e/3a21abf3cd467d7876045335e681d276ac32492febe6d98ad89562d1a7e1/Jinja2-3.1.3.tar.gz" } ], "project_name": "jinja2", @@ -2040,7 +2048,7 @@ "MarkupSafe>=2.0" ], "requires_python": ">=3.7", - "version": "3.1.2" + "version": "3.1.3" }, { "artifacts": [ @@ -2105,13 +2113,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "e11e02cd8ae0a9de5c6c44abf5727df9f2581055afe00b22183f621ba3585805", - "url": "https://files.pythonhosted.org/packages/ab/ea/af6508f71d2bcbf4db538940120cc3d3f10287f62105e756bd315aa345b5/jupyter_core-5.5.0-py3-none-any.whl" + "hash": "c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7", + "url": "https://files.pythonhosted.org/packages/86/a1/354cade6907f2fbbd32d89872ec64b62406028e7645ac13acfdb5732829e/jupyter_core-5.7.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "880b86053bf298a8724994f95e99b99130659022a4f7f45f563084b6223861d3", - "url": "https://files.pythonhosted.org/packages/5c/3d/c75bda485eaf15cd430383deb0c441aa822679ea88c5b32cfc2013f678e1/jupyter_core-5.5.0.tar.gz" + "hash": "de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218", + "url": "https://files.pythonhosted.org/packages/c3/de/53a5c189e358dae95d4176c6075127822c9b00193e8d7b1a77003aab253d/jupyter_core-5.7.1.tar.gz" } ], "project_name": "jupyter-core", @@ -2132,7 +2140,7 @@ "traitlets>=5.3" ], "requires_python": ">=3.8", - "version": "5.5.0" + "version": "5.7.1" }, { "artifacts": [ @@ -2190,13 +2198,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "7d2c221a66a8165f3f81aacb958d26033d40d972fdb70213ab0a2e0627e29c86", - "url": "https://files.pythonhosted.org/packages/99/ca/f3532a61dce7dd52fbd38737a12e16cdc7699697e23287eb7addfdd93e3f/lark-1.1.8-py3-none-any.whl" + "hash": "a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db", + "url": "https://files.pythonhosted.org/packages/e7/9c/eef7c591e6dc952f3636cfe0df712c0f9916cedf317810a3bb53ccb65cdd/lark-1.1.9-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "7ef424db57f59c1ffd6f0d4c2b705119927f566b68c0fe1942dddcc0e44391a5", - "url": "https://files.pythonhosted.org/packages/12/1c/b466b58dacac15ffefce9bcb5128e18948a143849610a7d5300f31920be0/lark-1.1.8.tar.gz" + "hash": "15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba", + "url": "https://files.pythonhosted.org/packages/2c/e1/804b6196b3fbdd0f8ba785fc62837b034782a891d6f663eea2f30ca23cfa/lark-1.1.9.tar.gz" } ], "project_name": "lark", @@ -2207,7 +2215,7 @@ "regex; extra == \"regex\"" ], "requires_python": ">=3.6", - "version": "1.1.8" + "version": "1.1.9" }, { "artifacts": [ @@ -2246,13 +2254,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9", - "url": "https://files.pythonhosted.org/packages/24/3b/11fe92d68c6a42468ddab0cf03f454419b0788fff4e91ba46b8bebafeffd/Mako-1.3.0-py3-none-any.whl" + "hash": "463f03e04559689adaee25e0967778d6ad41285ed607dc1e7df0dd4e4df81f9e", + "url": "https://files.pythonhosted.org/packages/56/fc/a229b64f439c142142f584f78f63beb6b4514668e7e076e7d09b35fb18a0/Mako-1.3.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b", - "url": "https://files.pythonhosted.org/packages/a9/6e/6b41e654bbdcef90c6b9e7f280bf7cbd756dc2560ce76214f5cdbc4ddab5/Mako-1.3.0.tar.gz" + "hash": "baee30b9c61718e093130298e678abed0dbfa1b411fcc4c1ab4df87cd631a0f2", + "url": "https://files.pythonhosted.org/packages/9c/cf/947dfd8475332b603dc8cb46170a7c333415f6ff1b161896ee76b4358435/Mako-1.3.1.tar.gz" } ], "project_name": "mako", @@ -2263,7 +2271,7 @@ "pytest; extra == \"testing\"" ], "requires_python": ">=3.8", - "version": "1.3.0" + "version": "1.3.1" }, { "artifacts": [ @@ -2313,78 +2321,72 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", - "url": "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl" + "hash": "c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131", + "url": "https://files.pythonhosted.org/packages/0d/57/aa0a5a362ef161e6be4e8f574338bf531d136d1579cd6b3ee8d54f0c51f8/MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", - "url": "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl" + "hash": "e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7", + "url": "https://files.pythonhosted.org/packages/41/76/d710592bf23cecab5361a76acddbcd80386fb44a41bb680b8cde94ce753f/MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "url": "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl" + "hash": "abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb", + "url": "https://files.pythonhosted.org/packages/87/8a/04467357a6f40f013f9362d174e0cb2d9f91c77f56f4b73c3b0c8f620fff/MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", - "url": "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz" + "hash": "55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc", + "url": "https://files.pythonhosted.org/packages/98/8f/2d3694997f3eb2d3775950985a83194de77d8c7836c8f899116cc83a46de/MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", - "url": "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea", + "url": "https://files.pythonhosted.org/packages/9d/38/ba1ea63db85f87a5ceeccc157059652a6eb9c1b100483c6887ffbf993878/MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", - "url": "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl" + "hash": "3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6", + "url": "https://files.pythonhosted.org/packages/d3/0a/c6dfffacc5a9a17c97019cb7cbec67e5abfb65c59a58ecba270fa224f88d/MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", - "url": "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69", + "url": "https://files.pythonhosted.org/packages/de/9a/6d2e1b78fecf2a1318aa41b5962deb4f1168b9d2c095543fc465de82c0c7/MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", - "url": "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl" + "hash": "9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863", + "url": "https://files.pythonhosted.org/packages/e9/58/e6de8fd932e62d7a43174e700290cba3da08a502955de22141fbfced4b42/MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", - "url": "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f", + "url": "https://files.pythonhosted.org/packages/fb/5a/fb1326fe32913e663c8e2d6bdf7cde6f472e51f9c21f0768d9b9080fe7c5/MarkupSafe-2.1.4.tar.gz" } ], "project_name": "markupsafe", "requires_dists": [], "requires_python": ">=3.7", - "version": "2.1.3" + "version": "2.1.4" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c", - "url": "https://files.pythonhosted.org/packages/ed/3c/cebfdcad015240014ff08b883d1c0c427f2ba45ae8c6572851b6ef136cad/marshmallow-3.20.1-py3-none-any.whl" + "hash": "c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9", + "url": "https://files.pythonhosted.org/packages/57/e9/4368d49d3b462da16a3bac976487764a84dd85cef97232c7bd61f5bdedf3/marshmallow-3.20.2-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889", - "url": "https://files.pythonhosted.org/packages/e4/e0/3e49c0f91f3e8954806c1076f4eae2c95a9d3ed2546f267c683b877d327b/marshmallow-3.20.1.tar.gz" + "hash": "4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd", + "url": "https://files.pythonhosted.org/packages/03/81/763717b3448e5d3a3906f27ab2ffedc9a495e8077946f54b8033967d29fd/marshmallow-3.20.2.tar.gz" } ], "project_name": "marshmallow", "requires_dists": [ - "alabaster==0.7.13; extra == \"docs\"", - "autodocsumm==0.2.11; extra == \"docs\"", - "flake8-bugbear==23.7.10; extra == \"dev\"", - "flake8-bugbear==23.7.10; extra == \"lint\"", - "flake8==6.0.0; extra == \"dev\"", - "flake8==6.0.0; extra == \"lint\"", - "mypy==1.4.1; extra == \"dev\"", - "mypy==1.4.1; extra == \"lint\"", + "alabaster==0.7.15; extra == \"docs\"", + "autodocsumm==0.2.12; extra == \"docs\"", "packaging>=17.0", "pre-commit<4.0,>=2.4; extra == \"dev\"", "pre-commit<4.0,>=2.4; extra == \"lint\"", @@ -2396,11 +2398,11 @@ "simplejson; extra == \"tests\"", "sphinx-issues==3.0.1; extra == \"docs\"", "sphinx-version-warning==1.1.2; extra == \"docs\"", - "sphinx==7.0.1; extra == \"docs\"", + "sphinx==7.2.6; extra == \"docs\"", "tox; extra == \"dev\"" ], "requires_python": ">=3.8", - "version": "3.20.1" + "version": "3.20.2" }, { "artifacts": [ @@ -2766,13 +2768,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", - "url": "https://files.pythonhosted.org/packages/31/16/70be3b725073035aa5fc3229321d06e22e73e3e09f6af78dcfdf16c7636c/platformdirs-4.0.0-py3-none-any.whl" + "hash": "11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380", + "url": "https://files.pythonhosted.org/packages/be/53/42fe5eab4a09d251a76d0043e018172db324a23fcdac70f77a551c11f618/platformdirs-4.1.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731", - "url": "https://files.pythonhosted.org/packages/31/28/e40d24d2e2eb23135f8533ad33d582359c7825623b1e022f9d460def7c05/platformdirs-4.0.0.tar.gz" + "hash": "906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420", + "url": "https://files.pythonhosted.org/packages/62/d1/7feaaacb1a3faeba96c06e6c5091f90695cc0f94b7e8e1a3a3fe2b33ff9a/platformdirs-4.1.0.tar.gz" } ], "project_name": "platformdirs", @@ -2785,11 +2787,10 @@ "pytest-mock>=3.11.1; extra == \"test\"", "pytest>=7.4; extra == \"test\"", "sphinx-autodoc-typehints>=1.24; extra == \"docs\"", - "sphinx>=7.1.1; extra == \"docs\"", - "typing-extensions>=4.7.1; python_version < \"3.8\"" + "sphinx>=7.1.1; extra == \"docs\"" ], - "requires_python": ">=3.7", - "version": "4.0.0" + "requires_python": ">=3.8", + "version": "4.1.0" }, { "artifacts": [ @@ -2818,13 +2819,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2", - "url": "https://files.pythonhosted.org/packages/1f/9d/be9b01085bbd67a71c4b6aa02518fade8104e7a2224e5de5e947811d7176/prompt_toolkit-3.0.41-py3-none-any.whl" + "hash": "a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6", + "url": "https://files.pythonhosted.org/packages/ee/fd/ca7bf3869e7caa7a037e23078539467b433a4e01eebd93f77180ab927766/prompt_toolkit-3.0.43-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0", - "url": "https://files.pythonhosted.org/packages/d9/7b/7d88d94427e1e179e0a62818e68335cf969af5ca38033c0ca02237ab6ee7/prompt_toolkit-3.0.41.tar.gz" + "hash": "3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d", + "url": "https://files.pythonhosted.org/packages/cc/c6/25b6a3d5cd295304de1e32c9edbcf319a52e965b339629d37d42bb7126ca/prompt_toolkit-3.0.43.tar.gz" } ], "project_name": "prompt-toolkit", @@ -2832,7 +2833,7 @@ "wcwidth" ], "requires_python": ">=3.7.0", - "version": "3.0.41" + "version": "3.0.43" }, { "artifacts": [ @@ -2871,28 +2872,28 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57", - "url": "https://files.pythonhosted.org/packages/9e/cb/e4b83c27eea66bc255effc967053f6fce7c14906dd9b43a348ead9f0cfea/psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl" + "hash": "d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8", + "url": "https://files.pythonhosted.org/packages/05/33/2d74d588408caedd065c2497bdb5ef83ce6082db01289a1e1147f6639802/psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl" }, { "algorithm": "sha256", - "hash": "748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4", - "url": "https://files.pythonhosted.org/packages/19/06/4e3fa3c1b79271e933c5ddbad3a48aa2c3d5f592a0fb7c037f3e0f619f4d/psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c", + "url": "https://files.pythonhosted.org/packages/90/c7/6dc0a455d111f68ee43f27793971cf03fe29b6ef972042549db29eec39a2/psutil-5.9.8.tar.gz" }, { "algorithm": "sha256", - "hash": "e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a", - "url": "https://files.pythonhosted.org/packages/2d/01/beb7331fc6c8d1c49dd051e3611379bfe379e915c808e1301506027fce9d/psutil-5.9.6.tar.gz" + "hash": "8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421", + "url": "https://files.pythonhosted.org/packages/b3/bd/28c5f553667116b2598b9cc55908ec435cb7f77a34f2bff3e3ca765b0f78/psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c", - "url": "https://files.pythonhosted.org/packages/61/c8/e684dea1912943347922ab5c05efc94b4ff3d7470038e8afbe3941ef9efe/psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4", + "url": "https://files.pythonhosted.org/packages/c5/4f/0e22aaa246f96d6ac87fe5ebb9c5a693fbe8877f537a1022527c47ca43c5/psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a", - "url": "https://files.pythonhosted.org/packages/f8/36/35b12441ba1bc6684c9215191f955415196ca57ca85d88e313bec7f2cf8e/psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl" + "hash": "aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81", + "url": "https://files.pythonhosted.org/packages/e7/e3/07ae864a636d70a8a6f58da27cb1179192f1140d5d1da10886ade9405797/psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl" } ], "project_name": "psutil", @@ -2904,7 +2905,7 @@ "wmi; sys_platform == \"win32\" and extra == \"test\"" ], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7", - "version": "5.9.6" + "version": "5.9.8" }, { "artifacts": [ @@ -3058,54 +3059,54 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9", - "url": "https://files.pythonhosted.org/packages/38/84/d429fafe45c0a32dc7d759fbb9b917282944d6852cdaf9e5dc743ea7b0aa/pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl" + "hash": "210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4", + "url": "https://files.pythonhosted.org/packages/b5/bf/798630923b67f4201059c2d690105998f20a6a8fb9b5ab68d221985155b3/pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", - "hash": "e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434", - "url": "https://files.pythonhosted.org/packages/00/e6/73931df4046e34a6354d323b4a5b5c18e5184f4a08687806ee3353c81a6b/pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c", + "url": "https://files.pythonhosted.org/packages/0d/08/01987ab75ca789247a88c8b2f0ce374ef7d319e79589e0842e316a272662/pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl" }, { "algorithm": "sha256", - "hash": "bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e", - "url": "https://files.pythonhosted.org/packages/1a/72/acc37a491b95849b51a2cced64df62aaff6a5c82d26aca10bc99dbda025b/pycryptodome-3.19.0.tar.gz" + "hash": "76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a", + "url": "https://files.pythonhosted.org/packages/24/80/56a04e2ae622d7f38c1c01aef46a26c6b73a2ad15c9705a8e008b5befb03/pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl" }, { "algorithm": "sha256", - "hash": "61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f", - "url": "https://files.pythonhosted.org/packages/20/03/bd639213eba37d29a16d359b42def13def905a456358f8af52107fc4e8a4/pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl" + "hash": "fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128", + "url": "https://files.pythonhosted.org/packages/30/4b/cbc67cda0efd55d7ddcc98374c4b9c853022a595ed1d78dd15c961bc7f6e/pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl" }, { "algorithm": "sha256", - "hash": "d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1", - "url": "https://files.pythonhosted.org/packages/2d/8a/a05cb7434cda97da2491bee1418be9651ce99fec74bfe95320e153845853/pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c", + "url": "https://files.pythonhosted.org/packages/af/20/5f29ec45462360e7f61e8688af9fe4a0afae057edfabdada662e11bf97e7/pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", - "hash": "139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33", - "url": "https://files.pythonhosted.org/packages/82/b7/876ced7a574843bf6358da5a2faa910925a8b9eb79bb7f164d070dbf72ae/pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl" + "hash": "09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7", + "url": "https://files.pythonhosted.org/packages/b9/ed/19223a0a0186b8a91ebbdd2852865839237a21c74f1fbc4b8d5b62965239/pycryptodome-3.20.0.tar.gz" }, { "algorithm": "sha256", - "hash": "84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde", - "url": "https://files.pythonhosted.org/packages/b8/97/243bc2019517abb13051e197f33deaf69493bbe020846e6571c7a485a428/pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl" + "hash": "49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25", + "url": "https://files.pythonhosted.org/packages/e5/1f/6bc4beb4adc07b847e5d3fddbec4522c2c3aa05df9e61b91dc4eff6a4946/pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, { "algorithm": "sha256", - "hash": "542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb", - "url": "https://files.pythonhosted.org/packages/d6/df/295e56dca0b4834665626326359c5a1f3092287be56db4b316926df7ad0c/pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl" + "hash": "f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2", + "url": "https://files.pythonhosted.org/packages/ea/94/82ebfa5c83d980907ceebf79b00909a569d258bdfd9b0264d621fa752cfd/pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270", - "url": "https://files.pythonhosted.org/packages/da/63/43feb85e3183e4bcd0d3780579c8006f37e8bd2438812a210a4738728244/pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + "hash": "ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044", + "url": "https://files.pythonhosted.org/packages/ff/96/b0d494defb3346378086848a8ece5ddfd138a66c4a05e038fca873b2518c/pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl" } ], "project_name": "pycryptodome", "requires_dists": [], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", - "version": "3.19.0" + "version": "3.20.0" }, { "artifacts": [ @@ -3253,13 +3254,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", - "url": "https://files.pythonhosted.org/packages/f3/8c/f16efd81ca8e293b2cc78f111190a79ee539d0d5d36ccd49975cb3beac60/pytest-7.4.3-py3-none-any.whl" + "hash": "b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", + "url": "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5", - "url": "https://files.pythonhosted.org/packages/38/d4/174f020da50c5afe9f5963ad0fc5b56a4287e3586e3de5b3c8bce9c547b4/pytest-7.4.3.tar.gz" + "hash": "2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", + "url": "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz" } ], "project_name": "pytest", @@ -3282,22 +3283,23 @@ "xmlschema; extra == \"testing\"" ], "requires_python": ">=3.7", - "version": "7.4.3" + "version": "7.4.4" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "c2a892906192663f85030a6ab91304e508e546cddfe557d692d61ec57a1d946b", - "url": "https://files.pythonhosted.org/packages/69/6d/cfd6d654877f75e0368e4040f1cf0350dd9f427b578bf7b685af629f8167/pytest-dependency-0.5.1.tar.gz" + "hash": "934b0e6a39d95995062c193f7eaeed8a8ffa06ff1bcef4b62b0dc74a708bacc1", + "url": "https://files.pythonhosted.org/packages/7e/3b/317cc04e77d707d338540ca67b619df8f247f3f4c9f40e67bf5ea503ad94/pytest-dependency-0.6.0.tar.gz" } ], "project_name": "pytest-dependency", "requires_dists": [ - "pytest>=3.6.0" + "pytest>=3.7.0", + "setuptools" ], - "requires_python": null, - "version": "0.5.1" + "requires_python": ">=3.4", + "version": "0.6.0" }, { "artifacts": [ @@ -3474,6 +3476,24 @@ "requires_python": ">=3.6", "version": "24.0.1" }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "9d7ff7adec88f995c5bd2350e26914c1360b74aeb00162be64a9b2085a1ba551", + "url": "https://files.pythonhosted.org/packages/61/00/5b858682e91a68cbf33b718f5dc7709d39840b6214de497ea3dd59f03a8c/raftify-0.1.40-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "d92eeea30996f8c810df3fb4f4745ddb3a9cc7afec4c514877e3830ef5624805", + "url": "https://files.pythonhosted.org/packages/cb/56/f6d651c88650082dc08f4b839328c59c6b0e9068cd7a093d7bfd67db63d4/raftify-0.1.40.tar.gz" + } + ], + "project_name": "raftify", + "requires_dists": [], + "requires_python": ">=3.10", + "version": "0.1.40" + }, { "artifacts": [ { @@ -3614,13 +3634,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76", - "url": "https://files.pythonhosted.org/packages/75/ca/5399536cbd5889ca4532d4b8bbcd17efa0fe0be0da04e143667a4ff5644e/s3transfer-0.8.2-py3-none-any.whl" + "hash": "3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e", + "url": "https://files.pythonhosted.org/packages/12/bb/7e7912e18cd558e7880d9b58ffc57300b2c28ffba9882b3a54ba5ce3ebc4/s3transfer-0.10.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283", - "url": "https://files.pythonhosted.org/packages/5f/cc/7e3b8305e22d7dcb383d4e1a30126cfac3d54aea2bbd2dfd147e2eff4988/s3transfer-0.8.2.tar.gz" + "hash": "d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b", + "url": "https://files.pythonhosted.org/packages/a0/b5/4c570b08cb85fdcc65037b5229e00412583bb38d974efecb7ec3495f40ba/s3transfer-0.10.0.tar.gz" } ], "project_name": "s3transfer", @@ -3628,8 +3648,8 @@ "botocore<2.0a.0,>=1.33.2", "botocore[crt]<2.0a.0,>=1.33.2; extra == \"crt\"" ], - "requires_python": ">=3.7", - "version": "0.8.2" + "requires_python": ">=3.8", + "version": "0.10.0" }, { "artifacts": [ @@ -3700,13 +3720,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2", - "url": "https://files.pythonhosted.org/packages/bb/e1/ed2dd0850446b8697ad28d118df885ad04140c64ace06c4bd559f7c8a94f/setuptools-69.0.2-py3-none-any.whl" + "hash": "385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05", + "url": "https://files.pythonhosted.org/packages/55/3a/5121b58b578a598b269537e09a316ad2a94fdd561a2c6eb75cd68578cc6b/setuptools-69.0.3-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6", - "url": "https://files.pythonhosted.org/packages/4b/d9/d0cf66484b7e28a9c42db7e3929caed46f8b80478cd8c9bd38b7be059150/setuptools-69.0.2.tar.gz" + "hash": "be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78", + "url": "https://files.pythonhosted.org/packages/fc/c9/b146ca195403e0182a374e0ea4dbc69136bad3cd55bc293df496d625d0f7/setuptools-69.0.3.tar.gz" } ], "project_name": "setuptools", @@ -3758,7 +3778,7 @@ "wheel; extra == \"testing-integration\"" ], "requires_python": ">=3.8", - "version": "69.0.2" + "version": "69.0.3" }, { "artifacts": [ @@ -3782,18 +3802,23 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace", - "url": "https://files.pythonhosted.org/packages/ea/d0/ba24be8ae3371efd477435e46d117431ca7c458e6c2a06fff3b2aa1c6b74/SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "0892e7ac8bc76da499ad3ee8de8da4d7905a3110b952e2a35a940dab1ffa550e", + "url": "https://files.pythonhosted.org/packages/8c/72/8706c1508e3218dc8ddfff6f10df8bba122f4e22499fd48337dd67ad11cc/SQLAlchemy-1.4.51-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "7deeae5071930abb3669b5185abb6c33ddfd2398f87660fafdb9e6a5fb0f3f2f", + "url": "https://files.pythonhosted.org/packages/47/f3/9403aac7f66e202151712201d2485e8f6b575f418f65c1ca589352ebaf80/SQLAlchemy-1.4.51-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, { "algorithm": "sha256", - "hash": "3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf", - "url": "https://files.pythonhosted.org/packages/5a/0a/dabe332c40afebb0a979d3e66b34570fce2f8611bae19b186f0c69f54643/SQLAlchemy-1.4.50.tar.gz" + "hash": "eaeeb2464019765bc4340214fca1143081d49972864773f3f1e95dba5c7edc7d", + "url": "https://files.pythonhosted.org/packages/75/72/7862cedd279c2acf94fbc76ff990592e8a9730b0ca32696df56a28920965/SQLAlchemy-1.4.51-cp311-cp311-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", - "hash": "14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7", - "url": "https://files.pythonhosted.org/packages/82/60/9210ac87f2eecb047c291b9b415aad3d2a1931666b5489e5a2e474448e8c/SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "e7908c2025eb18394e32d65dd02d2e37e17d733cdbe7d78231c2b6d7eb20cdb9", + "url": "https://files.pythonhosted.org/packages/c8/56/5a8dcb01ef7b68904f2a3224343d4ab3674b5cc8f48f7cefb0701bc75ab8/SQLAlchemy-1.4.51.tar.gz" } ], "project_name": "sqlalchemy", @@ -3830,7 +3855,7 @@ "typing-extensions!=3.10.0.1; extra == \"aiosqlite\"" ], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7", - "version": "1.4.50" + "version": "1.4.51" }, { "artifacts": [ @@ -3927,26 +3952,25 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "b6a3340738e3c2223049bb6a4fbce059e4f942a4480b8fd146b816ce5228a8ec", - "url": "https://files.pythonhosted.org/packages/df/15/957b85b4175982feccd0df2c0221c3879b981dd71da2dd82692d66a8d2e7/textual-0.43.2-py3-none-any.whl" + "hash": "da79df2e138f6de51bda84a1ee1460936bb2ecf5527ca2d47b9b59c584323327", + "url": "https://files.pythonhosted.org/packages/b7/3f/61a8eae44a5ecffde54a69146e2885bd1b1a877ed46148b2c4e97eb8384b/textual-0.47.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "7f4f84f1ae753aa39290659dc0bb0aab06abb7e37aa3041349c86940698c6b54", - "url": "https://files.pythonhosted.org/packages/5c/9e/000c6910f8390cdec827e738e52c6ab21501d7ce7b18d31293dd53879db8/textual-0.43.2.tar.gz" + "hash": "4b82e317884bb1092f693f474c319ceb068b5a0b128b121f1aa53a2d48b4b80c", + "url": "https://files.pythonhosted.org/packages/24/51/57eb835afc9569d32b5979ecbf3bf73f8ece8700ebffab3bac7ff29f92e4/textual-0.47.1.tar.gz" } ], "project_name": "textual", "requires_dists": [ - "importlib-metadata>=4.11.3", "markdown-it-py[linkify,plugins]>=2.1.0", "rich>=13.3.3", "tree-sitter<0.21.0,>=0.20.1; extra == \"syntax\"", "tree_sitter_languages>=1.7.0; extra == \"syntax\"", "typing-extensions<5.0.0,>=4.4.0" ], - "requires_python": "<4.0,>=3.7", - "version": "0.43.2" + "requires_python": "<4.0,>=3.8", + "version": "0.47.1" }, { "artifacts": [ @@ -4089,13 +4113,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "f14949d23829023013c47df20b4a76ccd1a85effb786dc060f34de7948361b33", - "url": "https://files.pythonhosted.org/packages/a7/1d/7d07e1b152b419a8a9c7f812eeefd408a0610d869489ee2e86973486713f/traitlets-5.14.0-py3-none-any.whl" + "hash": "2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74", + "url": "https://files.pythonhosted.org/packages/45/34/5dc77fdc7bb4bd198317eea5679edf9cc0a186438b5b19dbb9062fb0f4d5/traitlets-5.14.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "fcdaa8ac49c04dfa0ed3ee3384ef6dfdb5d6f3741502be247279407679296772", - "url": "https://files.pythonhosted.org/packages/25/a0/2feefaa884a7eaa83934476091ecfb2a3bc3b61c1ed98db3da0fbbf46e73/traitlets-5.14.0.tar.gz" + "hash": "8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e", + "url": "https://files.pythonhosted.org/packages/f1/b9/19206da568095bbf2e57f9f7f7cb6b3b2af2af2670f8c83c23a53d6c00cd/traitlets-5.14.1.tar.gz" } ], "project_name": "traitlets", @@ -4111,7 +4135,7 @@ "sphinx; extra == \"docs\"" ], "requires_python": ">=3.8", - "version": "5.14.0" + "version": "5.14.1" }, { "artifacts": [ @@ -4156,19 +4180,19 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "5d6719e8148cb2a9c4ea46dad86d50d3b675c46a940adca698533a8d2216d53d", - "url": "https://files.pythonhosted.org/packages/70/d7/6a640b932d669779e403ae92e6416f948f595b202a4492763a0901149d42/types_aiofiles-23.2.0.0-py3-none-any.whl" + "hash": "7324f9a9f7200c1f4986a9e40a42b548290f707b967709f30b280e99fdacbd99", + "url": "https://files.pythonhosted.org/packages/f8/94/8d707d1e813ac3d3f750ff744a1901af86e9b80399e90f55ee9c2c4acb7a/types_aiofiles-23.2.0.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b6a7127bd232e0802532837b84140b1cd5df19ee60bea3a5699720d2b583361b", - "url": "https://files.pythonhosted.org/packages/23/06/3cc83ed8fd5c31af6bc19197ab6fb0a31efb63b64d0aa30934ebe5193f97/types-aiofiles-23.2.0.0.tar.gz" + "hash": "ef4fa3072441c58beaadbd0d07ba18e89beff49c71648dd223e2ca861f3dac53", + "url": "https://files.pythonhosted.org/packages/41/aa/e4c821e437e4e733be93c9c54a1043b6faa24dd0cf7294cf7ee1245e6624/types-aiofiles-23.2.0.20240106.tar.gz" } ], "project_name": "types-aiofiles", "requires_dists": [], - "requires_python": null, - "version": "23.2.0.0" + "requires_python": ">=3.8", + "version": "23.2.0.20240106" }, { "artifacts": [ @@ -4230,39 +4254,39 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "00171433653265843b7469ddb9f3c86d698668064cc33ef10537822156130ebf", - "url": "https://files.pythonhosted.org/packages/f5/b3/cd6c2344b922f116ba9d6b6e9f57f6d2f13a3514bd971b3e19a19383d28f/types_pyOpenSSL-23.3.0.0-py3-none-any.whl" + "hash": "47a7eedbd18b7bcad17efebf1c53416148f5a173918a6d75027e75e32fe039ae", + "url": "https://files.pythonhosted.org/packages/16/63/c44743e26b7214c926a4ab9153d99f8cb5181520fa9169c56b232949b3ac/types_pyOpenSSL-23.3.0.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "5ffb077fe70b699c88d5caab999ae80e192fe28bf6cda7989b7e79b1e4e2dcd3", - "url": "https://files.pythonhosted.org/packages/a4/43/32f874b5fa2240932d758c8f4350632e4edd19e662032ff4992ebcd8a882/types-pyOpenSSL-23.3.0.0.tar.gz" + "hash": "3d6f3462bec0c260caadf93fbb377225c126661b779c7d9ab99b6dad5ca10db9", + "url": "https://files.pythonhosted.org/packages/69/ec/378e4368ecabef95cc136d86d95ecf5098592fdfaa044774e20de90396d9/types-pyOpenSSL-23.3.0.20240106.tar.gz" } ], "project_name": "types-pyopenssl", "requires_dists": [ "cryptography>=35.0.0" ], - "requires_python": ">=3.7", - "version": "23.3.0.0" + "requires_python": ">=3.8", + "version": "23.3.0.20240106" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9", - "url": "https://files.pythonhosted.org/packages/1c/af/5af2e2a02bc464c1c7818c260606343020b96c0d5b64f637d9e91aee24fe/types_python_dateutil-2.8.19.14-py3-none-any.whl" + "hash": "efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2", + "url": "https://files.pythonhosted.org/packages/28/50/8ed67814241e2684369f4b8b881c7d31a0816e76c8690ea8518017a35b7e/types_python_dateutil-2.8.19.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b", - "url": "https://files.pythonhosted.org/packages/1b/2d/f189e5c03c22700c4ce5aece4b51bb73fa8adcfd7848629de0fb78af5f6f/types-python-dateutil-2.8.19.14.tar.gz" + "hash": "1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f", + "url": "https://files.pythonhosted.org/packages/9b/47/2a9e51ae8cf48cea0089ff6d9d13fff60701f8c9bf72adaee0c4e5dc88f9/types-python-dateutil-2.8.19.20240106.tar.gz" } ], "project_name": "types-python-dateutil", "requires_dists": [], - "requires_python": null, - "version": "2.8.19.14" + "requires_python": ">=3.8", + "version": "2.8.19.20240106" }, { "artifacts": [ @@ -4286,13 +4310,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "94fc61118601fb4f79206b33b9f4344acff7ca1d7bba67834987fb0efcf6a770", - "url": "https://files.pythonhosted.org/packages/5f/3e/63dabc15151422daad7b0f2f82c0a37d1edc5d9278dfc05fe7f9cc42cd3d/types_redis-4.6.0.11-py3-none-any.whl" + "hash": "912de6507b631934bd225cdac310b04a58def94391003ba83939e5a10e99568d", + "url": "https://files.pythonhosted.org/packages/d1/a1/d4b177e9dbcdca2f6e881bbe255a2d3880f534eb64416bb27d17ee6c98e0/types_redis-4.6.0.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "c8cfc84635183deca2db4a528966c5566445fd3713983f0034fb0f5a09e0890d", - "url": "https://files.pythonhosted.org/packages/a9/b9/68d600512c73df22477f09236a978dbc00bd0f6215308c2deac69e6f9aa0/types-redis-4.6.0.11.tar.gz" + "hash": "2b2fa3a78f84559616242d23f86de5f4130dfd6c3b83fb2d8ce3329e503f756e", + "url": "https://files.pythonhosted.org/packages/62/c0/696140b04c5f61ddb28f0a398093952d72e205b565fd8e9a286b235f2e41/types-redis-4.6.0.20240106.tar.gz" } ], "project_name": "types-redis", @@ -4300,80 +4324,80 @@ "cryptography>=35.0.0", "types-pyOpenSSL" ], - "requires_python": ">=3.7", - "version": "4.6.0.11" + "requires_python": ">=3.8", + "version": "4.6.0.20240106" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "8c86195bae2ad81e6dea900a570fe9d64a59dbce2b11cc63c046b03246ea77bf", - "url": "https://files.pythonhosted.org/packages/66/a3/9800e99c4081cb1bff0ec10bf6effb93edc9253ce2ec6db50be1a9d57053/types_setuptools-69.0.0.0-py3-none-any.whl" + "hash": "7409e774c69e1810cb45052dbaed839fc30302e86a3ff945172ef2a2e7ab46f8", + "url": "https://files.pythonhosted.org/packages/e6/fb/d0816b1029d3204ad4ed7f7d11786afd3cdcfebc4f0c0586cf5d1177cf82/types_setuptools-69.0.0.20240115-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b0a06219f628c6527b2f8ce770a4f47550e00d3e8c3ad83e2dc31bc6e6eda95d", - "url": "https://files.pythonhosted.org/packages/aa/dc/27d4819c27b504bbd2e8ae5aa907fe72c70af8ff90b8b4cdb96316275844/types-setuptools-69.0.0.0.tar.gz" + "hash": "1a9c863899f40cbe2053d0cd1d00ddef0330b492335467d018f73c1fec9462a3", + "url": "https://files.pythonhosted.org/packages/07/64/bd87e18c7cdd21636140750ba7a3f4c6551c1baef969445a927185ec374a/types-setuptools-69.0.0.20240115.tar.gz" } ], "project_name": "types-setuptools", "requires_dists": [], - "requires_python": ">=3.7", - "version": "69.0.0.0" + "requires_python": ">=3.8", + "version": "69.0.0.20240115" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "1591a09430a3035326da5fdb71692d0b3cc36b25a440cc5929ca6241f3984705", - "url": "https://files.pythonhosted.org/packages/e6/c8/829ed94ad26c81e180a4fd5fe205fbd179a622154e5b14e86ed285ca73e9/types_six-1.16.21.9-py3-none-any.whl" + "hash": "3658c9e36e9cb003e522655b01b9ca39bd0db61b6383b3e7d0d10d14f873b338", + "url": "https://files.pythonhosted.org/packages/2c/eb/dc5d230d27323d72f2c387cd73a71229cf4687c908caa93e8674d7e5f163/types_six-1.16.21.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "746e6c25b8c48b3c8ab9efe7f68022839111de423d35ba4b206b88b12d75f233", - "url": "https://files.pythonhosted.org/packages/e1/17/dfc4897a8a4a636e7bb9a219d7a01a339e1c8c4d403fd9d363ded7a98e75/types-six-1.16.21.9.tar.gz" + "hash": "c83908b4925583e973eb9971ef2bd60dbab647611e10e9cd588d2bef415bfe68", + "url": "https://files.pythonhosted.org/packages/08/0a/f677c7cb11b5dd7ae8281b06ef4b3c86043acf5fe18ee169c421b5e23f90/types-six-1.16.21.20240106.tar.gz" } ], "project_name": "types-six", "requires_dists": [], - "requires_python": null, - "version": "1.16.21.9" + "requires_python": ">=3.8", + "version": "1.16.21.20240106" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "462d1b62e01728416e8277614d6a3eb172d53a8efaf04a04a973ff2dd45238f6", - "url": "https://files.pythonhosted.org/packages/d3/56/eb7d7585e046b75a1fcf137410ea5c0e0e32d1f88e716dcf79943501f06b/types_tabulate-0.9.0.3-py3-none-any.whl" + "hash": "0378b7b6fe0ccb4986299496d027a6d4c218298ecad67199bbd0e2d7e9d335a1", + "url": "https://files.pythonhosted.org/packages/f0/17/d53c0bb370100313df6800e9096bdfc27b32b8e4a9390bfb35bc4b17db78/types_tabulate-0.9.0.20240106-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "197651f9d6467193cd166d8500116a6d3a26f2a4eb2db093bc9535ee1c0be55e", - "url": "https://files.pythonhosted.org/packages/23/78/70a84863aad790a5d1cb03df6fee1b769674c18991b412041496564baeb4/types-tabulate-0.9.0.3.tar.gz" + "hash": "c9b6db10dd7fcf55bd1712dd3537f86ddce72a08fd62bb1af4338c7096ce947e", + "url": "https://files.pythonhosted.org/packages/cf/9d/65b82ce032fd1cc4df752461175a800c1cfc336461f07ceff10c6a5913eb/types-tabulate-0.9.0.20240106.tar.gz" } ], "project_name": "types-tabulate", "requires_dists": [], - "requires_python": null, - "version": "0.9.0.3" + "requires_python": ">=3.8", + "version": "0.9.0.20240106" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", - "url": "https://files.pythonhosted.org/packages/24/21/7d397a4b7934ff4028987914ac1044d3b7d52712f30e2ac7a2ae5bc86dd0/typing_extensions-4.8.0-py3-none-any.whl" + "hash": "af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd", + "url": "https://files.pythonhosted.org/packages/b7/f4/6a90020cd2d93349b442bfcb657d0dc91eee65491600b2cb1d388bc98e6b/typing_extensions-4.9.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef", - "url": "https://files.pythonhosted.org/packages/1f/7a/8b94bb016069caa12fc9f587b28080ac33b4fbb8ca369b98bc0a4828543e/typing_extensions-4.8.0.tar.gz" + "hash": "23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "url": "https://files.pythonhosted.org/packages/0c/1d/eb26f5e75100d531d7399ae800814b069bc2ed2a7410834d57374d010d96/typing_extensions-4.9.0.tar.gz" } ], "project_name": "typing-extensions", "requires_dists": [], "requires_python": ">=3.8", - "version": "4.8.0" + "version": "4.9.0" }, { "artifacts": [ @@ -4516,13 +4540,13 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c", - "url": "https://files.pythonhosted.org/packages/31/b1/a59de0ad3aabb17523a39804f4c6df3ae87ead053a4e25362ae03d73d03a/wcwidth-0.2.12-py2.py3-none-any.whl" + "hash": "3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "url": "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02", - "url": "https://files.pythonhosted.org/packages/d7/12/63deef355537f290d5282a67bb7bdd165266e4eca93cd556707a325e5a24/wcwidth-0.2.12.tar.gz" + "hash": "72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", + "url": "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz" } ], "project_name": "wcwidth", @@ -4530,19 +4554,19 @@ "backports.functools-lru-cache>=1.2.1; python_version < \"3.2\"" ], "requires_python": null, - "version": "0.2.12" + "version": "0.2.13" }, { "artifacts": [ { "algorithm": "sha256", - "hash": "084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24", - "url": "https://files.pythonhosted.org/packages/c4/3c/1892ce394828c43d4f65248ebdee3854114266b75d1f5915cb211155ad7b/websocket_client-1.6.4-py3-none-any.whl" + "hash": "f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588", + "url": "https://files.pythonhosted.org/packages/1e/70/1e88138a9afbed1d37093b85f0bebc3011623c4f47c166431599fe9d6c93/websocket_client-1.7.0-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df", - "url": "https://files.pythonhosted.org/packages/cb/eb/19eadbb717ef032749853ef5eb1c28e9ca974711e28bccd4815913ba5546/websocket-client-1.6.4.tar.gz" + "hash": "10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6", + "url": "https://files.pythonhosted.org/packages/20/07/2a94288afc0f6c9434d6709c5320ee21eaedb2f463ede25ed9cf6feff330/websocket-client-1.7.0.tar.gz" } ], "project_name": "websocket-client", @@ -4554,7 +4578,7 @@ "wsaccel; extra == \"optional\"" ], "requires_python": ">=3.8", - "version": "1.6.4" + "version": "1.7.0" }, { "artifacts": [ @@ -4638,44 +4662,6 @@ "requires_python": ">=3.7", "version": "1.8.2" }, - { - "artifacts": [ - { - "algorithm": "sha256", - "hash": "0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "url": "https://files.pythonhosted.org/packages/d9/66/48866fc6b158c81cc2bfecc04c480f105c6040e8b077bc54c634b4a67926/zipp-3.17.0-py3-none-any.whl" - }, - { - "algorithm": "sha256", - "hash": "84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0", - "url": "https://files.pythonhosted.org/packages/58/03/dd5ccf4e06dec9537ecba8fcc67bbd4ea48a2791773e469e73f94c3ba9a6/zipp-3.17.0.tar.gz" - } - ], - "project_name": "zipp", - "requires_dists": [ - "big-O; extra == \"testing\"", - "furo; extra == \"docs\"", - "jaraco.functools; extra == \"testing\"", - "jaraco.itertools; extra == \"testing\"", - "jaraco.packaging>=9.3; extra == \"docs\"", - "jaraco.tidelift>=1.4; extra == \"docs\"", - "more-itertools; extra == \"testing\"", - "pytest-black>=0.3.7; platform_python_implementation != \"PyPy\" and extra == \"testing\"", - "pytest-checkdocs>=2.4; extra == \"testing\"", - "pytest-cov; extra == \"testing\"", - "pytest-enabler>=2.2; extra == \"testing\"", - "pytest-ignore-flaky; extra == \"testing\"", - "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"", - "pytest-ruff; extra == \"testing\"", - "pytest>=6; extra == \"testing\"", - "rst.linker>=1.9; extra == \"docs\"", - "sphinx-lint; extra == \"docs\"", - "sphinx<7.2.5; extra == \"docs\"", - "sphinx>=3.5; extra == \"docs\"" - ], - "requires_python": ">=3.8", - "version": "3.17.0" - }, { "artifacts": [ { @@ -4766,6 +4752,7 @@ "python-dotenv~=0.20.0", "python-json-logger>=2.0.1", "pyzmq~=24.0.1", + "raftify==0.1.40", "redis[hiredis]==4.5.5", "rich~=13.6", "setproctitle~=1.3.2", diff --git a/requirements.txt b/requirements.txt index 07fac5aede1..e5126f019b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -93,3 +93,5 @@ types-tabulate backend.ai-krunner-alpine==5.1.0 backend.ai-krunner-static-gnu==4.1.0 + +raftify==0.1.40 diff --git a/src/ai/backend/common/distributed.py b/src/ai/backend/common/distributed.py index b52fe2414a1..04ca6c1dd64 100644 --- a/src/ai/backend/common/distributed.py +++ b/src/ai/backend/common/distributed.py @@ -1,10 +1,12 @@ from __future__ import annotations +import abc import asyncio import logging from typing import TYPE_CHECKING, Callable, Final from aiomonitor.task import preserve_termination_log +from raftify import RaftNode from .logging import BraceStyleAdapter @@ -16,7 +18,77 @@ log = BraceStyleAdapter(logging.getLogger(__spec__.name)) # type: ignore[name-defined] -class GlobalTimer: +class AbstractGlobalTimer(metaclass=abc.ABCMeta): + @abc.abstractmethod + async def generate_tick(self) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def join(self) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def leave(self) -> None: + raise NotImplementedError + + +class RaftGlobalTimer(AbstractGlobalTimer): + """ + Executes the given async function only once in the given interval, + uniquely among multiple manager instances across multiple nodes. + """ + + _event_producer: Final[EventProducer] + + def __init__( + self, + raft_node: RaftNode, + event_producer: EventProducer, + event_factory: Callable[[], AbstractEvent], + interval: float = 10.0, + initial_delay: float = 0.0, + ) -> None: + self._event_producer = event_producer + self._event_factory = event_factory + self._stopped = False + self.interval = interval + self.initial_delay = initial_delay + self.raft_node = raft_node + + async def generate_tick(self) -> None: + try: + await asyncio.sleep(self.initial_delay) + if self._stopped: + return + while True: + try: + if self._stopped: + return + if await self.raft_node.is_leader(): + await self._event_producer.produce_event(self._event_factory()) + if self._stopped: + return + await asyncio.sleep(self.interval) + except asyncio.TimeoutError: # timeout raised from etcd lock + log.warn("timeout raised while trying to acquire lock. retrying...") + except asyncio.CancelledError: + pass + + async def join(self) -> None: + self._tick_task = asyncio.create_task(self.generate_tick()) + + async def leave(self) -> None: + self._stopped = True + await asyncio.sleep(0) + if not self._tick_task.done(): + try: + self._tick_task.cancel() + await self._tick_task + except asyncio.CancelledError: + pass + + +class DistributedLockGlobalTimer(AbstractGlobalTimer): """ Executes the given async function only once in the given interval, uniquely among multiple manager instances across multiple nodes. diff --git a/src/ai/backend/manager/api/context.py b/src/ai/backend/manager/api/context.py index d8a989b15e1..184162e3347 100644 --- a/src/ai/backend/manager/api/context.py +++ b/src/ai/backend/manager/api/context.py @@ -1,8 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, cast import attrs +from raftify import Raft, RaftNode if TYPE_CHECKING: from ai.backend.common.bgtask import BackgroundTaskManager @@ -26,6 +27,25 @@ class BaseContext: pass +class RaftClusterContext: + _cluster: Optional[Raft] = None + + def use_raft(self) -> bool: + return self._cluster is not None + + @property + def cluster(self) -> Raft: + return cast(Raft, self._cluster) + + @cluster.setter + def cluster(self, rhs: Raft) -> None: + self._cluster = rhs + + @property + def raft_node(self) -> RaftNode: + return self.cluster.get_raft_node() + + @attrs.define(slots=True, auto_attribs=True, init=False) class RootContext(BaseContext): pidx: int @@ -53,3 +73,4 @@ class RootContext(BaseContext): error_monitor: ErrorPluginContext stats_monitor: StatsPluginContext background_task_manager: BackgroundTaskManager + raft_ctx: RaftClusterContext diff --git a/src/ai/backend/manager/api/logs.py b/src/ai/backend/manager/api/logs.py index 49449ea03d3..c75fb5d5d02 100644 --- a/src/ai/backend/manager/api/logs.py +++ b/src/ai/backend/manager/api/logs.py @@ -14,7 +14,11 @@ from dateutil.relativedelta import relativedelta from ai.backend.common import validators as tx -from ai.backend.common.distributed import GlobalTimer +from ai.backend.common.distributed import ( + AbstractGlobalTimer, + DistributedLockGlobalTimer, + RaftGlobalTimer, +) from ai.backend.common.events import AbstractEvent, EmptyEventArgs, EventHandler from ai.backend.common.logging import BraceStyleAdapter from ai.backend.common.types import AgentId, LogSeverity @@ -234,7 +238,7 @@ async def log_cleanup_task(app: web.Application, src: AgentId, event: DoLogClean @attrs.define(slots=True, auto_attribs=True, init=False) class PrivateContext: - log_cleanup_timer: GlobalTimer + log_cleanup_timer: AbstractGlobalTimer log_cleanup_timer_evh: EventHandler[web.Application, DoLogCleanupEvent] @@ -246,14 +250,24 @@ async def init(app: web.Application) -> None: app, log_cleanup_task, ) - app_ctx.log_cleanup_timer = GlobalTimer( - root_ctx.distributed_lock_factory(LockID.LOCKID_LOG_CLEANUP_TIMER, 20.0), - root_ctx.event_producer, - lambda: DoLogCleanupEvent(), - 20.0, - initial_delay=17.0, - task_name="log_cleanup_task", - ) + + if root_ctx.raft_ctx.use_raft(): + app_ctx.log_cleanup_timer = RaftGlobalTimer( + root_ctx.raft_ctx.raft_node, + root_ctx.event_producer, + lambda: DoLogCleanupEvent(), + 20.0, + initial_delay=17.0, + ) + else: + app_ctx.log_cleanup_timer = DistributedLockGlobalTimer( + root_ctx.distributed_lock_factory(LockID.LOCKID_LOG_CLEANUP_TIMER, 20.0), + root_ctx.event_producer, + lambda: DoLogCleanupEvent(), + 20.0, + initial_delay=17.0, + task_name="log_cleanup_task", + ) await app_ctx.log_cleanup_timer.join() diff --git a/src/ai/backend/manager/cli/__main__.py b/src/ai/backend/manager/cli/__main__.py index a6c20bc564d..394d69f6d44 100644 --- a/src/ai/backend/manager/cli/__main__.py +++ b/src/ai/backend/manager/cli/__main__.py @@ -1,16 +1,20 @@ from __future__ import annotations import asyncio +import json import logging import pathlib import subprocess import sys from datetime import datetime from functools import partial +from typing import Any import click from more_itertools import chunked +from raftify import Peers, RaftServiceClient, cli_main from setproctitle import setproctitle +from tabulate import tabulate from ai.backend.cli.params import BoolExprType, OptionalType from ai.backend.cli.types import ExitCode @@ -19,6 +23,7 @@ from ai.backend.common.logging import BraceStyleAdapter from ai.backend.common.types import LogSeverity from ai.backend.common.validators import TimeDuration +from ai.backend.manager.raft.utils import register_custom_deserializer from .context import CLIContext, redis_ctx @@ -326,6 +331,83 @@ async def _clear_terminated_sessions(): asyncio.run(_clear_terminated_sessions()) +async def inspect_node_status(cli_ctx: CLIContext) -> None: + raft_configs = cli_ctx.local_config["raft"] + table = [] + headers = ["ENDPOINT", "NODE ID", "IS LEADER", "RAFT TERM", "RAFT APPLIED INDEX"] + + if raft_configs is not None: + initial_peers = Peers({ + int(entry["node-id"]): f"{entry['host']}:{entry['port']}" + for entry in raft_configs["peers"] + }) + + peers: dict[str, Any] | None = None + for _, peer_addr in initial_peers.items(): + raft_client = await RaftServiceClient.build(peer_addr) + try: + resp = await raft_client.get_peers() + peers = json.loads(resp) + except Exception as e: + print(f"Failed to getting peers from {peer_addr}: {e}") + continue + + if peers is None: + print("No peers are available!") + return + + for node_id in sorted(peers.keys()): + peer = peers[node_id] + raft_client = await RaftServiceClient.build(peer["addr"]) + + try: + node_debugging_info = json.loads(await raft_client.debug_node()) + except Exception as e: + print(f"Failed to getting debugging info from {peer['addr']}: {e}") + table.append([peer["addr"], "(Invalid response)"]) + + is_leader = node_debugging_info["node_id"] == node_debugging_info["leader_id"] + table.append([ + peer["addr"], + node_debugging_info["node_id"], + is_leader, + node_debugging_info["term"], + node_debugging_info["raft_log"]["applied"], + ]) + + table = [headers, *sorted(table, key=lambda x: str(x[0]))] + print( + tabulate(table, headers="firstrow", tablefmt="grid", stralign="center", numalign="center") + ) + + +@main.command() +@click.pass_obj +def status(cli_ctx: CLIContext) -> None: + """ + Collect and print each manager process's status. + """ + asyncio.run(inspect_node_status(cli_ctx)) + + +async def handle_raft_cli_main(argv: list[str]): + await cli_main(argv) + + +@main.command() +@click.pass_obj +@click.argument("args", nargs=-1, type=click.UNPROCESSED) +def raft(cli_ctx: CLIContext, args) -> None: + register_custom_deserializer() + + argv = sys.argv + # Remove "backend.ai", "mgr", "raft" from the argv + argv[:3] = [] + argv.insert(0, "raftify-cli") + + asyncio.run(handle_raft_cli_main(argv)) + + @main.group(cls=LazyGroup, import_name="ai.backend.manager.cli.dbschema:cli") def schema(): """Command set for managing the database schema.""" diff --git a/src/ai/backend/manager/config.py b/src/ai/backend/manager/config.py index 8ece7c69ffc..d3510cb1dfd 100644 --- a/src/ai/backend/manager/config.py +++ b/src/ai/backend/manager/config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + """ Configuration Schema on etcd ---------------------------- @@ -172,8 +174,6 @@ - {instance-id}: 1 # just a membership set """ -from __future__ import annotations - import json import logging import os @@ -216,6 +216,7 @@ SlotTypes, current_resource_slots, ) +from ai.backend.manager.types import RaftLogLovel from ..manager.defs import INTRINSIC_SLOTS from .api import ManagerStatus @@ -298,6 +299,44 @@ t.Key("log-scheduler-ticks", default=False): t.ToBool, t.Key("periodic-sync-stats", default=False): t.ToBool, }).allow_extra("*"), + t.Key("raft", default=None): ( + t.Dict({ + # Cluster configurations + t.Key("cluster-leader-id", default=1): t.Int, + ## This would be useful when adding new RaftNodes to an existing cluster without restarting the server. + t.Key("bootstrap-done", default=False): t.ToBool, + ## Set this to the max(node_ids) when joining RaftNodes to another cluster. + t.Key("node-id-offset", default=0): t.Int, + t.Key("restore-wal-from", default=None): t.Int | t.Null, + t.Key("restore-wal-snapshot-from", default=None): t.Int | t.Null, + t.Key("peers"): t.List( + t.Dict({ + t.Key("node-id"): t.Int, + t.Key("host"): t.String, + t.Key("port"): t.Int, + }) + ), + # Storage configurations + t.Key("log-dir"): t.String, + # Logging configurations + t.Key("log-level", default=None): tx.Enum(RaftLogLovel) | t.Null, + # Raft core configurations + t.Key("heartbeat-tick", default=None): t.Int | t.Null, + t.Key("election-tick", default=None): t.Int | t.Null, + t.Key("min-election-tick", default=None): t.Int | t.Null, + t.Key("max-election-tick", default=None): t.Int | t.Null, + t.Key("max-committed-size-per-ready", default=None): t.Int | t.Null, + t.Key("max-size-per-msg", default=None): t.Int | t.Null, + t.Key("max-inflight-msgs", default=None): t.Int | t.Null, + t.Key("check-quorum", default=None): t.ToBool | t.Null, + t.Key("batch-append", default=None): t.ToBool | t.Null, + t.Key("max-uncommitted-size", default=None): t.Int | t.Null, + t.Key("skip-bcast-commit", default=None): t.ToBool | t.Null, + t.Key("pre-vote", default=None): t.ToBool | t.Null, + t.Key("priority", default=None): t.Int | t.Null, + }).allow_extra("*") + | t.Null + ), }) .merge(config.etcd_config_iv) .allow_extra("*") @@ -341,6 +380,7 @@ "threshold": {}, }, }, + "raft": None, } container_registry_iv = t.Dict({ diff --git a/src/ai/backend/manager/idle.py b/src/ai/backend/manager/idle.py index 33790f4ae5f..c6db7ab63a7 100644 --- a/src/ai/backend/manager/idle.py +++ b/src/ai/backend/manager/idle.py @@ -35,7 +35,11 @@ import ai.backend.common.validators as tx from ai.backend.common import msgpack, redis_helper from ai.backend.common.defs import REDIS_LIVE_DB, REDIS_STAT_DB -from ai.backend.common.distributed import GlobalTimer +from ai.backend.common.distributed import ( + AbstractGlobalTimer, + DistributedLockGlobalTimer, + RaftGlobalTimer, +) from ai.backend.common.events import ( AbstractEvent, DoIdleCheckEvent, @@ -58,13 +62,14 @@ SessionTypes, ) from ai.backend.common.utils import nmget +from ai.backend.manager.api.context import RaftClusterContext +from ai.backend.manager.types import DistributedLockFactory from .defs import DEFAULT_ROLE, LockID from .models.kernel import LIVE_STATUS, kernels from .models.keypair import keypairs from .models.resource_policy import keypair_resource_policies from .models.user import users -from .types import DistributedLockFactory if TYPE_CHECKING: from sqlalchemy.ext.asyncio import AsyncConnection as SAConnection @@ -169,6 +174,7 @@ class RemainingTimeType(str, enum.Enum): class IdleCheckerHost: + timer: AbstractGlobalTimer check_interval: ClassVar[float] = DEFAULT_CHECK_INTERVAL def __init__( @@ -177,6 +183,7 @@ def __init__( shared_config: SharedConfig, event_dispatcher: EventDispatcher, event_producer: EventProducer, + raft_ctx: RaftClusterContext, lock_factory: DistributedLockFactory, ) -> None: self._checkers: list[BaseIdleChecker] = [] @@ -199,6 +206,7 @@ def __init__( self._grace_period_checker: NewUserGracePeriodChecker = NewUserGracePeriodChecker( event_dispatcher, self._redis_live, self._redis_stat ) + self.raft_ctx = raft_ctx def add_checker(self, checker: BaseIdleChecker): if self._frozen: @@ -218,13 +226,22 @@ async def start(self) -> None: ) for checker in self._checkers: await checker.populate_config(raw_config.get(checker.name) or {}) - self.timer = GlobalTimer( - self._lock_factory(LockID.LOCKID_IDLE_CHECK_TIMER, self.check_interval), - self._event_producer, - lambda: DoIdleCheckEvent(), - self.check_interval, - task_name="idle_checker", - ) + + if self.raft_ctx.use_raft(): + self.timer = RaftGlobalTimer( + self.raft_ctx.raft_node, + self._event_producer, + lambda: DoIdleCheckEvent(), + self.check_interval, + ) + else: + self.timer = DistributedLockGlobalTimer( + self._lock_factory(LockID.LOCKID_IDLE_CHECK_TIMER, self.check_interval), + self._event_producer, + lambda: DoIdleCheckEvent(), + self.check_interval, + ) + self._evh_idle_check = self._event_dispatcher.consume( DoIdleCheckEvent, None, @@ -855,7 +872,7 @@ async def check_idleness( if (window_size <= 0) or (math.isinf(window_size) and window_size > 0): return True - # Wait until the time "interval" is passed after the last udpated time. + # Wait until the time "interval" is passed after the last updated time. t = await redis_helper.execute(self._redis_live, lambda r: r.time()) util_now: float = t[0] + (t[1] / (10**6)) raw_util_last_collected = await redis_helper.execute( @@ -1057,6 +1074,7 @@ async def init_idle_checkers( shared_config: SharedConfig, event_dispatcher: EventDispatcher, event_producer: EventProducer, + raft_ctx: RaftClusterContext, lock_factory: DistributedLockFactory, ) -> IdleCheckerHost: """ @@ -1068,6 +1086,7 @@ async def init_idle_checkers( shared_config, event_dispatcher, event_producer, + raft_ctx, lock_factory, ) checker_init_args = (event_dispatcher, checker_host._redis_live, checker_host._redis_stat) diff --git a/src/ai/backend/manager/raft/BUILD b/src/ai/backend/manager/raft/BUILD new file mode 100644 index 00000000000..73574424040 --- /dev/null +++ b/src/ai/backend/manager/raft/BUILD @@ -0,0 +1 @@ +python_sources(name="src") diff --git a/src/ai/backend/manager/raft/__init__.py b/src/ai/backend/manager/raft/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/ai/backend/manager/raft/logger.py b/src/ai/backend/manager/raft/logger.py new file mode 100644 index 00000000000..7c089316435 --- /dev/null +++ b/src/ai/backend/manager/raft/logger.py @@ -0,0 +1,41 @@ +from ai.backend.manager.types import RaftLogLovel + + +class Logger: + def __init__(self, level: RaftLogLovel, logger) -> None: + self.level = level + self.logger = logger + + def trace(self, message): + if self.level in [RaftLogLovel.TRACE]: + self.logger.info(message) + + def debug(self, message): + if self.level in [RaftLogLovel.TRACE, RaftLogLovel.DEBUG]: + self.logger.info(message) + + def info(self, message): + if self.level in [RaftLogLovel.TRACE, RaftLogLovel.DEBUG, RaftLogLovel.INFO]: + self.logger.info(message) + + def warn(self, message): + if self.level in [ + RaftLogLovel.TRACE, + RaftLogLovel.DEBUG, + RaftLogLovel.INFO, + RaftLogLovel.WARN, + ]: + self.logger.info(message) + + def error(self, message): + if self.level in [ + RaftLogLovel.TRACE, + RaftLogLovel.DEBUG, + RaftLogLovel.INFO, + RaftLogLovel.WARN, + RaftLogLovel.ERROR, + ]: + self.logger.info(message) + + def fatal(self, message): + self.logger.info(message) diff --git a/src/ai/backend/manager/raft/state_machine.py b/src/ai/backend/manager/raft/state_machine.py new file mode 100644 index 00000000000..e63b1434f86 --- /dev/null +++ b/src/ai/backend/manager/raft/state_machine.py @@ -0,0 +1,48 @@ +import pickle +from typing import Optional + + +class SetCommand: + """ + Represent simple key-value command. + Use pickle to serialize the data. + """ + + def __init__(self, key: str, value: str) -> None: + self.key = key + self.value = value + + def encode(self) -> bytes: + return pickle.dumps(self.__dict__) + + @classmethod + def decode(cls, packed: bytes) -> "SetCommand": + unpacked = pickle.loads(packed) + return cls(unpacked["key"], unpacked["value"]) + + +class HashStore: + """ + A simple key-value store that stores data in memory. + Use pickle to serialize the data. + """ + + def __init__(self): + self._store = dict() + + def get(self, key: str) -> Optional[str]: + return self._store.get(key) + + def as_dict(self) -> dict: + return self._store + + def apply(self, msg: bytes) -> bytes: + message = SetCommand.decode(msg) + self._store[message.key] = message.value + return msg + + def snapshot(self) -> bytes: + return pickle.dumps(self._store) + + def restore(self, snapshot: bytes) -> None: + self._store = pickle.loads(snapshot) diff --git a/src/ai/backend/manager/raft/utils.py b/src/ai/backend/manager/raft/utils.py new file mode 100644 index 00000000000..ad0509185d1 --- /dev/null +++ b/src/ai/backend/manager/raft/utils.py @@ -0,0 +1,101 @@ +import pickle +from typing import Any + +from aiohttp import web +from aiohttp.web import RouteTableDef +from raftify import ( + Raft, + set_confchange_context_deserializer, + set_confchangev2_context_deserializer, + set_entry_context_deserializer, + set_entry_data_deserializer, + set_fsm_deserializer, + set_log_entry_deserializer, + set_message_context_deserializer, + set_snapshot_data_deserializer, +) + +from ai.backend.manager.raft.state_machine import HashStore, SetCommand + +routes = RouteTableDef() +""" +APIs of the web servers to interact with the RaftServers. +""" + + +@routes.get("/get/{id}") +async def get(request: web.Request) -> web.Response: + store: HashStore = request.app["state"]["store"] + id = request.match_info["id"] + return web.Response(text=store.get(id)) + + +@routes.get("/leader") +async def leader(request: web.Request) -> web.Response: + raft: Raft = request.app["state"]["raft"] + leader_id = str(await raft.get_raft_node().get_leader_id()) + return web.Response(text=leader_id) + + +@routes.get("/size") +async def size(request: web.Request) -> web.Response: + raft: Raft = request.app["state"]["raft"] + size = str(await raft.get_raft_node().get_cluster_size()) + return web.Response(text=size) + + +@routes.get("/put/{id}/{value}") +async def put(request: web.Request) -> web.Response: + raft: Raft = request.app["state"]["raft"] + id, value = request.match_info["id"], request.match_info["value"] + message = SetCommand(id, value) + + await raft.get_raft_node().propose(message.encode()) + return web.Response(text="OK") + + +class WebServer: + """ + Simple webserver for Raft cluster testing. + Do not use this class for anything other than testing purposes. + """ + + def __init__(self, addr: str, state: dict[str, Any]): + self.app = web.Application() + self.app.add_routes(routes) + self.app["state"] = state + self.host, self.port = addr.split(":") + self.runner = None + + async def run(self): + self.runner = web.AppRunner(self.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, self.host, self.port) + await self.site.start() + + +def pickle_deserialize(data: bytes) -> str | None: + if data == b"": + return None + + if pickle.PROTO in data: + r = pickle.loads(data[data.index(pickle.PROTO) :]) + return r + + # Not pickle data + return None + + +def register_custom_deserializer() -> None: + """ + Initialize the custom deserializers. + """ + + set_confchange_context_deserializer(pickle_deserialize) + set_confchangev2_context_deserializer(pickle_deserialize) + set_entry_context_deserializer(pickle_deserialize) + set_entry_data_deserializer(pickle_deserialize) + set_message_context_deserializer(pickle_deserialize) + set_snapshot_data_deserializer(pickle_deserialize) + set_log_entry_deserializer(pickle_deserialize) + set_fsm_deserializer(pickle_deserialize) diff --git a/src/ai/backend/manager/scheduler/dispatcher.py b/src/ai/backend/manager/scheduler/dispatcher.py index a6c550fb885..df6171d9a5e 100644 --- a/src/ai/backend/manager/scheduler/dispatcher.py +++ b/src/ai/backend/manager/scheduler/dispatcher.py @@ -33,7 +33,11 @@ from ai.backend.common import redis_helper from ai.backend.common.defs import REDIS_LIVE_DB -from ai.backend.common.distributed import GlobalTimer +from ai.backend.common.distributed import ( + AbstractGlobalTimer, + DistributedLockGlobalTimer, + RaftGlobalTimer, +) from ai.backend.common.events import ( AgentStartedEvent, CoalescingOptions, @@ -61,15 +65,16 @@ SessionId, aobject, ) +from ai.backend.manager.api.context import RaftClusterContext +from ai.backend.manager.defs import SERVICE_MAX_RETRIES, LockID +from ai.backend.manager.models.agent import AgentRow from ai.backend.manager.models.session import _build_session_fetch_query from ai.backend.manager.types import DistributedLockFactory from ai.backend.plugin.entrypoint import scan_entrypoints from ..api.exceptions import GenericBadRequest, InstanceNotAvailable, SessionNotFound -from ..defs import SERVICE_MAX_RETRIES, LockID from ..exceptions import convert_to_status_data from ..models import ( - AgentRow, AgentStatus, EndpointLifecycle, EndpointRow, @@ -149,21 +154,22 @@ def load_scheduler( class SchedulerDispatcher(aobject): - config: LocalConfig + local_config: LocalConfig shared_config: SharedConfig registry: AgentRegistry db: SAEngine event_dispatcher: EventDispatcher event_producer: EventProducer - schedule_timer: GlobalTimer - prepare_timer: GlobalTimer - scale_timer: GlobalTimer + schedule_timer: AbstractGlobalTimer + prepare_timer: AbstractGlobalTimer + scale_timer: AbstractGlobalTimer redis_live: RedisConnectionInfo def __init__( self, + raft_ctx: RaftClusterContext, local_config: LocalConfig, shared_config: SharedConfig, event_dispatcher: EventDispatcher, @@ -171,6 +177,7 @@ def __init__( lock_factory: DistributedLockFactory, registry: AgentRegistry, ) -> None: + self.raft_ctx = raft_ctx self.local_config = local_config self.shared_config = shared_config self.event_dispatcher = event_dispatcher @@ -201,32 +208,54 @@ async def __ainit__(self) -> None: evd.consume(DoScheduleEvent, None, self.schedule, coalescing_opts) evd.consume(DoPrepareEvent, None, self.prepare) evd.consume(DoScaleEvent, None, self.scale_services) - self.schedule_timer = GlobalTimer( - self.lock_factory(LockID.LOCKID_SCHEDULE_TIMER, 10.0), - self.event_producer, - lambda: DoScheduleEvent(), - interval=10.0, - task_name="schedule_timer", - ) - self.prepare_timer = GlobalTimer( - self.lock_factory(LockID.LOCKID_PREPARE_TIMER, 10.0), - self.event_producer, - lambda: DoPrepareEvent(), - interval=10.0, - initial_delay=5.0, - task_name="prepare_timer", - ) - self.scale_timer = GlobalTimer( - self.lock_factory(LockID.LOCKID_SCALE_TIMER, 10.0), - self.event_producer, - lambda: DoScaleEvent(), - interval=10.0, - initial_delay=7.0, - task_name="scale_timer", - ) + + if self.raft_ctx.use_raft(): + self.schedule_timer = RaftGlobalTimer( + self.raft_ctx.raft_node, + self.event_producer, + lambda: DoScheduleEvent(), + interval=10.0, + ) + self.prepare_timer = RaftGlobalTimer( + self.raft_ctx.raft_node, + self.event_producer, + lambda: DoPrepareEvent(), + interval=10.0, + initial_delay=5.0, + ) + self.scale_timer = RaftGlobalTimer( + self.raft_ctx.raft_node, + self.event_producer, + lambda: DoScaleEvent(), + interval=10.0, + initial_delay=7.0, + ) + else: + self.schedule_timer = DistributedLockGlobalTimer( + self.lock_factory(LockID.LOCKID_SCHEDULE_TIMER, 10.0), + self.event_producer, + lambda: DoScheduleEvent(), + interval=10.0, + ) + self.prepare_timer = DistributedLockGlobalTimer( + self.lock_factory(LockID.LOCKID_PREPARE_TIMER, 10.0), + self.event_producer, + lambda: DoPrepareEvent(), + interval=10.0, + initial_delay=5.0, + ) + self.scale_timer = DistributedLockGlobalTimer( + self.lock_factory(LockID.LOCKID_SCALE_TIMER, 10.0), + self.event_producer, + lambda: DoScaleEvent(), + interval=10.0, + initial_delay=7.0, + ) + await self.schedule_timer.join() await self.prepare_timer.join() await self.scale_timer.join() + log.info("Session scheduler started") async def close(self) -> None: @@ -234,7 +263,6 @@ async def close(self) -> None: tg.create_task(self.scale_timer.leave()) tg.create_task(self.prepare_timer.leave()) tg.create_task(self.schedule_timer.leave()) - await self.redis_live.close() log.info("Session scheduler stopped") async def schedule( @@ -280,52 +308,33 @@ def _pipeline(r: Redis) -> RedisPipeline: ) try: - # The schedule() method should be executed with a global lock - # as its individual steps are composed of many short-lived transactions. - async with self.lock_factory(LockID.LOCKID_SCHEDULE, 60): - async with self.db.begin_readonly_session() as db_sess: - # query = ( - # sa.select(ScalingGroupRow) - # .join(ScalingGroupRow.agents.and_(AgentRow.status == AgentStatus.ALIVE)) - # ) - query = ( - sa.select(AgentRow.scaling_group) - .where(AgentRow.status == AgentStatus.ALIVE) - .group_by(AgentRow.scaling_group) - ) - result = await db_sess.execute(query) - schedulable_scaling_groups = [row.scaling_group for row in result.fetchall()] - for sgroup_name in schedulable_scaling_groups: - try: - await self._schedule_in_sgroup( - sched_ctx, - sgroup_name, - ) - await redis_helper.execute( - self.redis_live, - lambda r: r.hset( - redis_key, - "resource_group", - sgroup_name, - ), - ) - except InstanceNotAvailable as e: - # Proceed to the next scaling group and come back later. - log.debug( - "schedule({}): instance not available ({})", - sgroup_name, - e.extra_msg, - ) - except Exception as e: - log.exception("schedule({}): scheduling error!\n{}", sgroup_name, repr(e)) - await redis_helper.execute( - self.redis_live, - lambda r: r.hset( - redis_key, - "finish_time", - datetime.now(tzutc()).isoformat(), - ), + async with self.db.begin_readonly_session() as db_sess: + # query = ( + # sa.select(ScalingGroupRow) + # .join(ScalingGroupRow.agents.and_(AgentRow.status == AgentStatus.ALIVE)) + # ) + query = ( + sa.select(AgentRow.scaling_group) + .where(AgentRow.status == AgentStatus.ALIVE) + .group_by(AgentRow.scaling_group) ) + result = await db_sess.execute(query) + schedulable_scaling_groups = [row.scaling_group for row in result.fetchall()] + for sgroup_name in schedulable_scaling_groups: + try: + await self._schedule_in_sgroup( + sched_ctx, + sgroup_name, + ) + except InstanceNotAvailable as e: + # Proceed to the next scaling group and come back later. + log.debug( + "schedule({}): instance not available ({})", + sgroup_name, + e.extra_msg, + ) + except Exception as e: + log.exception("schedule({}): scheduling error!\n{}", sgroup_name, repr(e)) except DBAPIError as e: if getattr(e.orig, "pgcode", None) == "55P03": log.info( @@ -710,6 +719,7 @@ async def _schedule_single_node_session( log_fmt = _log_fmt.get("") log_args = _log_args.get(tuple()) requested_architectures = set(k.architecture for k in sess_ctx.kernels) + if len(requested_architectures) > 1: raise GenericBadRequest( "Cannot assign multiple kernels with different architectures' single node session", @@ -1231,91 +1241,90 @@ def _pipeline(r: Redis) -> RedisPipeline: known_slot_types, ) try: - async with self.lock_factory(LockID.LOCKID_PREPARE, 600): - now = datetime.now(tzutc()) + now = datetime.now(tzutc()) - async def _mark_session_preparing() -> Sequence[SessionRow]: - async with self.db.begin_session() as db_sess: - update_query = ( - sa.update(KernelRow) - .values( - status=KernelStatus.PREPARING, - status_changed=now, - status_info="", - status_data={}, - status_history=sql_json_merge( - KernelRow.status_history, - (), - { - KernelStatus.PREPARING.name: now.isoformat(), - }, - ), - ) - .where( - (KernelRow.status == KernelStatus.SCHEDULED), - ) - ) - await db_sess.execute(update_query) - update_sess_query = ( - sa.update(SessionRow) - .values( - status=SessionStatus.PREPARING, - # status_changed=now, - status_info="", - status_data={}, - status_history=sql_json_merge( - SessionRow.status_history, - (), - { - SessionStatus.PREPARING.name: now.isoformat(), - }, - ), - ) - .where(SessionRow.status == SessionStatus.SCHEDULED) - .returning(SessionRow.id) + async def _mark_session_preparing() -> Sequence[SessionRow]: + async with self.db.begin_session() as db_sess: + update_query = ( + sa.update(KernelRow) + .values( + status=KernelStatus.PREPARING, + status_changed=now, + status_info="", + status_data={}, + status_history=sql_json_merge( + KernelRow.status_history, + (), + { + KernelStatus.PREPARING.name: now.isoformat(), + }, + ), ) - rows = (await db_sess.execute(update_sess_query)).fetchall() - if len(rows) == 0: - return [] - target_session_ids = [r["id"] for r in rows] - select_query = ( - sa.select(SessionRow) - .where(SessionRow.id.in_(target_session_ids)) - .options( - noload("*"), - selectinload(SessionRow.kernels).noload("*"), - ) + .where( + (KernelRow.status == KernelStatus.SCHEDULED), ) - result = await db_sess.execute(select_query) - return result.scalars().all() - - scheduled_sessions: Sequence[SessionRow] - scheduled_sessions = await execute_with_retry(_mark_session_preparing) - log.debug("prepare(): preparing {} session(s)", len(scheduled_sessions)) - async with ( - async_timeout.timeout(delay=50.0), - aiotools.PersistentTaskGroup() as tg, - ): - for scheduled_session in scheduled_sessions: - await self.registry.event_producer.produce_event( - SessionPreparingEvent( - scheduled_session.id, - scheduled_session.creation_id, + ) + await db_sess.execute(update_query) + update_sess_query = ( + sa.update(SessionRow) + .values( + status=SessionStatus.PREPARING, + # status_changed=now, + status_info="", + status_data={}, + status_history=sql_json_merge( + SessionRow.status_history, + (), + { + SessionStatus.PREPARING.name: now.isoformat(), + }, ), ) - tg.create_task( - self.start_session( - sched_ctx, - scheduled_session, - ) + .where(SessionRow.status == SessionStatus.SCHEDULED) + .returning(SessionRow.id) + ) + rows = (await db_sess.execute(update_sess_query)).fetchall() + if len(rows) == 0: + return [] + target_session_ids = [r["id"] for r in rows] + select_query = ( + sa.select(SessionRow) + .where(SessionRow.id.in_(target_session_ids)) + .options( + noload("*"), + selectinload(SessionRow.kernels).noload("*"), ) - - await redis_helper.execute( - self.redis_live, - lambda r: r.hset( - redis_key, "resource_group", scheduled_session.scaling_group_name - ), + ) + result = await db_sess.execute(select_query) + return result.scalars().all() + + scheduled_sessions: Sequence[SessionRow] + scheduled_sessions = await execute_with_retry(_mark_session_preparing) + log.debug("prepare(): preparing {} session(s)", len(scheduled_sessions)) + async with ( + async_timeout.timeout(delay=50.0), + aiotools.PersistentTaskGroup() as tg, + ): + for scheduled_session in scheduled_sessions: + await self.registry.event_producer.produce_event( + SessionPreparingEvent( + scheduled_session.id, + scheduled_session.creation_id, + ), + ) + tg.create_task( + self.start_session( + sched_ctx, + scheduled_session, ) + ) + + await redis_helper.execute( + self.redis_live, + lambda r: r.hset( + redis_key, "resource_group", scheduled_session.scaling_group_name + ), + ) await redis_helper.execute( self.redis_live, lambda r: r.hset( diff --git a/src/ai/backend/manager/server.py b/src/ai/backend/manager/server.py index 7d5ca895585..14b8d4139b0 100644 --- a/src/ai/backend/manager/server.py +++ b/src/ai/backend/manager/server.py @@ -31,6 +31,8 @@ import aiotools import click from aiohttp import web +from aiotools import process_index +from raftify import ClusterJoinTicket, Config, Peers, Raft, RaftConfig from setproctitle import setproctitle from ai.backend.common import redis_helper @@ -50,11 +52,14 @@ from ai.backend.common.plugin.monitor import INCREMENT from ai.backend.common.types import AgentSelectionStrategy, LogSeverity from ai.backend.common.utils import env_info +from ai.backend.manager.raft.logger import Logger as RaftLogger +from ai.backend.manager.raft.state_machine import HashStore +from ai.backend.manager.raft.utils import WebServer, register_custom_deserializer from . import __version__ from .agent_cache import AgentRPCCache from .api import ManagerStatus -from .api.context import RootContext +from .api.context import RaftClusterContext, RootContext from .api.exceptions import ( BackendError, GenericBadRequest, @@ -416,6 +421,7 @@ async def idle_checker_ctx(root_ctx: RootContext) -> AsyncIterator[None]: root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) await root_ctx.idle_checker_host.start() @@ -494,6 +500,7 @@ async def sched_dispatcher_ctx(root_ctx: RootContext) -> AsyncIterator[None]: from .scheduler.dispatcher import SchedulerDispatcher sched_dispatcher = await SchedulerDispatcher.new( + root_ctx.raft_ctx, root_ctx.local_config, root_ctx.shared_config, root_ctx.event_dispatcher, @@ -640,6 +647,101 @@ async def _force_terminate_hanging_sessions( await task +@actxmgr +async def raft_ctx(root_ctx: RootContext) -> AsyncIterator[None]: + register_custom_deserializer() + local_config = root_ctx.local_config + raft_configs = local_config.get("raft") + + if raft_configs is not None: + initial_peers = Peers({ + int(peer_config["node-id"]): f"{peer_config['host']}:{peer_config['port']}" + for peer_config in raft_configs["peers"] + }) + + raft_core_config = RaftConfig( + heartbeat_tick=raft_configs["heartbeat-tick"], + election_tick=raft_configs["election-tick"], + min_election_tick=raft_configs["min-election-tick"], + max_election_tick=raft_configs["max-election-tick"], + max_committed_size_per_ready=raft_configs["max-committed-size-per-ready"], + max_size_per_msg=raft_configs["max-size-per-msg"], + max_inflight_msgs=raft_configs["max-inflight-msgs"], + check_quorum=raft_configs["check-quorum"], + batch_append=raft_configs["batch-append"], + max_uncommitted_size=raft_configs["max-uncommitted-size"], + skip_bcast_commit=raft_configs["skip-bcast-commit"], + pre_vote=raft_configs["pre-vote"], + priority=raft_configs["priority"], + ) + + raft_cfg = Config( + log_dir=raft_configs["log-dir"], + compacted_log_dir=raft_configs["log-dir"], + restore_wal_from=raft_configs["restore-wal-from"], + restore_wal_snapshot_from=raft_configs["restore-wal-snapshot-from"], + raft_config=raft_core_config, + ) + + store = HashStore() + raft_logger = RaftLogger( + raft_configs["log-level"], + logging.getLogger(f"{__spec__.name}.raft"), # type: ignore + ) + + node_id = raft_configs["node-id-offset"] + process_index.get() + 1 + raft_addr = initial_peers.get(node_id) + leader_id = raft_configs["cluster-leader-id"] + leader_addr = initial_peers.get(leader_id) + + if node_id == leader_id: + root_ctx.raft_ctx.cluster = Raft.bootstrap_cluster( + node_id, + raft_addr, + store, # type: ignore + raft_cfg, + raft_logger, # type: ignore + initial_peers, + ) + raft_cluster = root_ctx.raft_ctx.cluster + raft_cluster.run() # type: ignore + else: + root_ctx.raft_ctx.cluster = Raft.new_follower( + node_id, + raft_addr, + store, # type: ignore + raft_cfg, + raft_logger, # type: ignore + initial_peers, + ) + raft_cluster = root_ctx.raft_ctx.cluster + raft_cluster.run() # type: ignore + + # Wait for the leader node's gRPC server ready + await asyncio.sleep(2) + + if raft_configs["bootstrap-done"]: + await raft_cluster.join( + ClusterJoinTicket( + node_id, + leader_id, + leader_addr, + initial_peers, + ) + ) + await raft_cluster.get_raft_node().set_bootstrap_done() + else: + await raft_cluster.member_bootstrap_ready(leader_addr, node_id, raft_logger) # type: ignore + + # Only for testing + asyncio.create_task( + WebServer(f"127.0.0.1:6025{node_id}", {"raft": raft_cluster, "store": store}).run() + ) + + # assert root_ctx.raft_ctx.cluster.raft_node is not None, "RaftNode not initialized properly!" + yield + + class background_task_ctx: def __init__(self, root_ctx: RootContext) -> None: self.root_ctx = root_ctx @@ -786,6 +888,7 @@ def build_root_app( database_ctx, distributed_lock_ctx, event_dispatcher_ctx, + raft_ctx, idle_checker_ctx, storage_manager_ctx, hook_plugin_ctx, @@ -848,6 +951,8 @@ async def server_main( root_app = build_root_app(pidx, _args[0], subapp_pkgs=global_subapp_pkgs) root_ctx: RootContext = root_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() + # Start aiomonitor. # Port is set by config (default=50100 + pidx). loop.set_debug(root_ctx.local_config["debug"]["asyncio"]) @@ -925,8 +1030,10 @@ async def server_main_logwrapper( _args: List[Any], ) -> AsyncIterator[None]: setproctitle(f"backend.ai: manager worker-{pidx}") + log_endpoint = _args[1] logger = Logger(_args[0]["logging"], is_master=False, log_endpoint=log_endpoint) + try: with logger: async with server_main(loop, pidx, _args): @@ -956,7 +1063,12 @@ async def server_main_logwrapper( help="Set the logging verbosity level", ) @click.pass_context -def main(ctx: click.Context, config_path: Path, log_level: str, debug: bool = False) -> None: +def main( + ctx: click.Context, + config_path: Path, + log_level: str, + debug: bool = False, +) -> None: """ Start the manager service as a foreground process. """ diff --git a/src/ai/backend/manager/types.py b/src/ai/backend/manager/types.py index 7d413594deb..5f0a4c342fb 100644 --- a/src/ai/backend/manager/types.py +++ b/src/ai/backend/manager/types.py @@ -41,3 +41,12 @@ class UserScope: class DistributedLockFactory(Protocol): def __call__(self, lock_id: LockID, lifetime_hint: float) -> AbstractDistributedLock: ... + + +class RaftLogLovel(str, enum.Enum): + TRACE = "trace" + DEBUG = "debug" + INFO = "info" + WARN = "warn" + ERROR = "error" + FATAL = "fatal" diff --git a/tests/common/test_distributed.py b/tests/common/test_distributed.py index c4cc6e558d6..59cc56fa0d7 100644 --- a/tests/common/test_distributed.py +++ b/tests/common/test_distributed.py @@ -18,7 +18,7 @@ from redis.asyncio import Redis from ai.backend.common import config -from ai.backend.common.distributed import GlobalTimer +from ai.backend.common.distributed import DistributedLockGlobalTimer from ai.backend.common.etcd import AsyncEtcd, ConfigScopes from ai.backend.common.events import AbstractEvent, EventDispatcher, EventProducer from ai.backend.common.lock import AbstractDistributedLock, EtcdLock, FileLock, RedisLock @@ -98,7 +98,7 @@ async def _tick(context: Any, source: AgentId, event: NoopEvent) -> None: ) event_dispatcher.consume(NoopEvent, None, _tick) - timer = GlobalTimer( + timer = DistributedLockGlobalTimer( lock_factory(), event_producer, lambda: NoopEvent(test_case_ns), @@ -150,7 +150,7 @@ async def _tick(context: Any, source: AgentId, event: NoopEvent) -> None: ConfigScopes.NODE: "node/i-test", }, ) - timer = GlobalTimer( + timer = DistributedLockGlobalTimer( EtcdLock(etcd_ctx.lock_name, etcd, timeout=None, debug=True), event_producer, lambda: NoopEvent(timer_ctx.test_case_ns), @@ -207,7 +207,7 @@ async def _tick(context: Any, source: AgentId, event: NoopEvent) -> None: ) event_dispatcher.consume(NoopEvent, None, _tick) - timer = GlobalTimer( + timer = DistributedLockGlobalTimer( self.lock_factory(), event_producer, lambda: NoopEvent(self.test_case_ns), @@ -398,7 +398,7 @@ async def _tick(context: Any, source: AgentId, event: NoopEvent) -> None: lock_path = Path(tempfile.gettempdir()) / f"{test_case_ns}.lock" request.addfinalizer(partial(lock_path.unlink, missing_ok=True)) for _ in range(10): - timer = GlobalTimer( + timer = DistributedLockGlobalTimer( FileLock(lock_path, timeout=0, debug=True), event_producer, lambda: NoopEvent(test_case_ns), diff --git a/tests/manager/test_idle_checker.py b/tests/manager/test_idle_checker.py index 2174ebd981c..7f9eb214e67 100644 --- a/tests/manager/test_idle_checker.py +++ b/tests/manager/test_idle_checker.py @@ -7,7 +7,7 @@ from ai.backend.common import msgpack, redis_helper from ai.backend.common.types import KernelId, SessionId, SessionTypes -from ai.backend.manager.api.context import RootContext +from ai.backend.manager.api.context import RaftClusterContext, RootContext from ai.backend.manager.idle import ( BaseIdleChecker, IdleCheckerHost, @@ -97,6 +97,7 @@ async def new_user_grace_period_checker( [".etcd"], ) root_ctx: RootContext = test_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() # test config grace_period = 30 @@ -116,6 +117,7 @@ async def new_user_grace_period_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -146,6 +148,7 @@ async def network_timeout_idle_checker( [".etcd"], ) root_ctx: RootContext = test_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() # test 1 # remaining time is positive and no grace period @@ -177,6 +180,7 @@ async def network_timeout_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -230,6 +234,7 @@ async def network_timeout_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -287,6 +292,7 @@ async def network_timeout_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -349,6 +355,7 @@ async def network_timeout_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -397,6 +404,7 @@ async def session_lifetime_checker( [".etcd"], ) root_ctx: RootContext = test_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() # test 1 # remaining time is positive and no grace period @@ -424,6 +432,7 @@ async def session_lifetime_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -471,6 +480,7 @@ async def session_lifetime_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -523,6 +533,7 @@ async def session_lifetime_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -577,6 +588,7 @@ async def session_lifetime_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) try: @@ -620,6 +632,7 @@ async def utilization_idle_checker__utilization( [".etcd"], ) root_ctx: RootContext = test_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() kernel_id = KernelId(uuid4()) expected = { @@ -665,6 +678,7 @@ async def utilization_idle_checker__utilization( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) await redis_helper.execute( @@ -704,6 +718,7 @@ async def utilization_idle_checker( [".etcd"], ) root_ctx: RootContext = test_app["_root.context"] + root_ctx.raft_ctx = RaftClusterContext() # test 1 # remaining time is positive and no utilization. @@ -764,6 +779,7 @@ async def utilization_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) await redis_helper.execute( @@ -846,6 +862,7 @@ async def utilization_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) await redis_helper.execute( @@ -928,6 +945,7 @@ async def utilization_idle_checker( root_ctx.shared_config, root_ctx.event_dispatcher, root_ctx.event_producer, + root_ctx.raft_ctx, root_ctx.distributed_lock_factory, ) await redis_helper.execute( diff --git a/tests/manager/test_scheduler.py b/tests/manager/test_scheduler.py index cafa6b72f3c..d625c966147 100644 --- a/tests/manager/test_scheduler.py +++ b/tests/manager/test_scheduler.py @@ -26,6 +26,7 @@ SessionId, SessionTypes, ) +from ai.backend.manager.api.context import RaftClusterContext from ai.backend.manager.defs import DEFAULT_ROLE from ai.backend.manager.models.agent import AgentRow from ai.backend.manager.models.image import ImageRow @@ -1105,6 +1106,7 @@ async def test_manually_assign_agent_available( candidate_agents = example_agents example_pending_sessions[0].kernels[0].agent = example_agents[0].id sess_ctx = example_pending_sessions[0] + raft_ctx = RaftClusterContext() dispatcher = SchedulerDispatcher( local_config=mock_local_config, @@ -1112,6 +1114,7 @@ async def test_manually_assign_agent_available( event_dispatcher=mock_event_dispatcher, event_producer=mock_event_producer, lock_factory=file_lock_factory, + raft_ctx=raft_ctx, registry=registry, )